Android手掌抑制功能的实现
近期须要实现一个功能,在Activity中有一个手写区域,为了更好的用户体验,须要满足即使整个手掌放在屏幕上时(android平板,屏幕比較大)也仅仅响应手写区域内的操作,即在支持多点触控的情况下,仅仅响应指定的区域,我将这个功能称作“手掌抑制”,即在手写时。手掌放在屏幕上面不做不论什么响应。
初看这个功能非常easy,依照之前处理listview、gridview里面的子view不能响应的方式,仅仅要在activity层不拦截向手写view传递的消息就可以实现想要的效果,但经过实际測试和对android消息机制的具体研究发现。要实现这个功能会有点小复杂。
一、android的消息传递机制:
1、基础知识:
(1) 全部Touch事件都被封装成了MotionEvent对象。包含Touch的位置、时间、历史记录以及第几个手指(多指触摸)等。
(2) 事件类型分为ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每一个事件都是以ACTION_DOWN開始ACTION_UP结束。
(3) 对事件的处理包含三类:
传递——dispatchTouchEvent()
拦截——onInterceptTouchEvent()
消费——onTouchEvent()和OnTouchListener
2、传递流程
(1) 事件从Activity.dispatchTouchEvent()開始传递,仅仅要没有被停止或拦截,从最上层的View(ViewGroup)開始一直往下(子View)传递。子View能够通过onTouchEvent()对事件进行处理。
(2) 事件由父View(ViewGroup)传递给子View,ViewGroup能够通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
(3) 假设事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件。事件会反向往上传递,这时父View(ViewGroup)能够进行消费,假设还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
(4) 假设View没有对ACTION_DOWN进行消费,之后的其它事件不会传递过来。
(5) OnTouchListener优先于onTouchEvent()对事件进行消费。
上面的消费即表示对应函数返回值为true。
3、实际情况:
可以响应事件处理方法的控件包含:ViewGroup、View、Activity,各类控件对三个事件响应处理方法的支持情况例如以下:
这三个控件。Activity是处于最外层的,消息的传递首先是系统回调消息给Activity。Activity将消息传递给每个ViewGroup,然后ViewGroup会将消息传递给对应地子View。
本文所描写叙述的手写控件是一个view,在有系统消息回调时仅仅有上层控件将消息分发下来,它才可以消费和处理这些消息。
二、问题现象:
接着我们进入正题,依照我在开篇介绍的那种处理方式,写一个手写view,在Activity和ViewGroup(自己定义一个Layout就可以)层将消息分发给该view,眼下的代码看上去是这样子的:
public class DrawView extends View {
	public DrawView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaintView( );
	}
	public DrawView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView( );
	}
	public DrawView(Context context) {
		super(context);
		initPaintView( );
	}
	public void clear() {
        if (null != mPath) {
            mPath.reset();
            invalidate();
        }
    }  
    private void initPaintView() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(5f);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	mViewWidth = MeasureSpec.getSize(widthMeasureSpec);   //获取ViewGroup宽度
    	mViewHeight = MeasureSpec.getSize(heightMeasureSpec);  //获取ViewGroup高度
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mPath, mPaint);
    }  
    public boolean inArea( float x, float y ){
    	return ( x >= 0 && x <= mViewWidth && y >= 0 && y <= mViewHeight)?true:false;
    }
	@Override
    public boolean onTouchEvent(MotionEvent event) {
    	float eventX = event.getX( );
        float eventY = event.getY( );
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            mPath.moveTo(eventX, eventY);
            mLastTouchX = eventX;
            mLastTouchY = eventY;
            invalidate( );
        }
            return true;
        case MotionEvent.ACTION_MOVE:{
        	drawView( event, eventX, eventY );
        }
        break;
        case MotionEvent.ACTION_UP:{
        	drawView( event, eventX, eventY );
        }
            break;
        default:
            return false;
        }  
        return true;
    }
    private void drawView( MotionEvent event, float eventX, float eventY ){
    	resetDirtyRect(eventX, eventY);
        int historySize = event.getHistorySize();
        for (int i = 0; i < historySize; i++) {
            float historicalX = event.getHistoricalX(i);
            float historicalY = event.getHistoricalY(i);
            getDirtyRect(historicalX, historicalY);
            mPath.lineTo(historicalX, historicalY);
        }  
        mPath.lineTo(eventX, eventY);
        invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.top - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.right + HALF_STROKE_WIDTH),
                (int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));
        mLastTouchX = eventX;
        mLastTouchY = eventY;
    }
    private void getDirtyRect(float historicalX, float historicalY) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX;
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX;
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY;
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY;
        }
    }  
    private void resetDirtyRect(float eventX, float eventY) {
        mDirtyRect.left = Math.min(mLastTouchX, eventX);
        mDirtyRect.right = Math.max(mLastTouchX, eventX);
        mDirtyRect.top = Math.min(mLastTouchY, eventY);
        mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
    }  
    private static final float STROKE_WIDTH = 5f;
    private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    private float mLastTouchX = 0;
    private float mLastTouchY = 0;
    private int mViewWidth = 0;
    private int mViewHeight = 0;
    private final RectF mDirtyRect = new RectF();
    private Paint mPaint = new Paint();
    private Path mPath = new Path();
}
你会发现,当你将activity的dispatchTouchEvent和viewgroup的dispatchTouchEvent、onInterceptTouchEvent方法的返回值都设置为false时,整个屏幕都不会响应不论什么消息了,当我们去掉activity和viewgroup的dispatchTouchEvent方法,仅仅有viewgroup的onInterceptTouchEvent返回值被置为false时,手写区域可以响应。但当手掌靠在手写区域外,在手写区域内手写就失效了,事实上和去掉viewgroupd的onInterceptTouchEvent方法效果是一样的,也就是说明这样的处理方式是不可行的。
三、解决方式:
加了同意分发消息的方法。不拦截向下分发消息反而还不行,这个问题详细原因我没有找到。个人觉得是系统针对activity的消息事件处理做了特殊处理,它的优先级是最高的,尽管能够复写它的消息分发dispatchTouchEvent方法,可是无论是返回true还是false结果都是屏幕不能响应不论什么操作。这一点有知道的大拿欢迎指点指点。
要实现本文想要的功能显然不能使用开篇讲到的方法,在研究这个问题的过程中,发现尽管activity的dispatchTouchEvent无法控制,但其onTouchEvent方法是有效的,仅仅要在屏幕的不论什么一个地方操作,onTouchEvent里面都会有打印消息,细致回想上面提到的android消息分发机制会发现,仅仅要我们在屏幕上操作时,模拟系统在activity的onTouchEvent方法里面向手写view派发消息就可以实现想要的功能。详细方法例如以下:
1、向下派发消息的实现以及view和activity之间的坐标转换:
如上面所讲,当在屏幕上操作时监听activity的onTouchEvent方法,将在手写控件内的操作派发给手写view就可以实现想要的功能。
(1)坐标转换:
本文的手写view基于activity居中的,它的坐标原点和activity的坐标原点不同样,为了推断在acitivity上操作的地方是否在手写view内。须要通过坐标转换之后才干推断:
如上图所看到的,在activity中推断一个点(x,y)是否在手写view里面时,转换成的坐标应该是(x-view.getLeft(),y-view.getTop())。
(2)眼下的view和activity看起来是这样子的:
DrawView:
public class DrawView extends View {
	public DrawView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaintView( );
	}
	public DrawView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView( );
	}
	public DrawView(Context context) {
		super(context);
		initPaintView( );
	}
	public void clear() {
        if (null != mPath) {
            mPath.reset();
            invalidate();
        }
    }  
    private void initPaintView() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(5f);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	mViewWidth = MeasureSpec.getSize(widthMeasureSpec);   //获取ViewGroup宽度
    	mViewHeight = MeasureSpec.getSize(heightMeasureSpec);  //获取ViewGroup高度
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mPath, mPaint);
    }  
    public boolean inArea( float x, float y ){
    	return ( x >= 0 && x <= mViewWidth && y >= 0 && y <= mViewHeight)?true:false;
    }
	@Override
    public boolean onTouchEvent(MotionEvent event) {
    	float eventX = -1;
        float eventY = -1;
        int pointId = 0;
    	int pointCnt = event.getPointerCount( );
    	for( int index = 0; index < pointCnt; index++ ){
    		if( inArea( event.getX( index ) - getLeft( ), event.getY( index ) - getTop( ) ) ){
    			pointId = index;
    			eventX = event.getX( index ) - getLeft( );
    			eventY = event.getY( index ) - getTop( );
    			break;
    		}
    	}
    	if( ( eventX == -1 || eventY == -1 ) || ( eventX == 0 || eventY == 0 ) ){
    		return false;
    	}
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            mPath.moveTo(eventX, eventY);
            mLastTouchX = eventX;
            mLastTouchY = eventY;
            invalidate( );
        }
            return true;
        case MotionEvent.ACTION_MOVE:{
        	drawView( event, eventX, eventY, pointId );
        }
        break;
        case MotionEvent.ACTION_UP:{
        	drawView( event, eventX, eventY, pointId );
        }
            break;
        default:
            return false;
        }  
        return true;
    }
    private void drawView( MotionEvent event, float eventX, float eventY, int pointId ){
    	resetDirtyRect(eventX, eventY);
        int historySize = event.getHistorySize();
        for (int i = 0; i < historySize; i++) {
            float historicalX = event.getHistoricalX(pointId,i) - getLeft( );
            float historicalY = event.getHistoricalY(pointId,i) - getTop( );
            getDirtyRect(historicalX, historicalY);
            mPath.lineTo(historicalX, historicalY);
        }  
        mPath.lineTo(eventX, eventY);
        invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.top - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.right + HALF_STROKE_WIDTH),
                (int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));
        mLastTouchX = eventX;
        mLastTouchY = eventY;
    }
    private void getDirtyRect(float historicalX, float historicalY) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX;
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX;
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY;
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY;
        }
    }  
    private void resetDirtyRect(float eventX, float eventY) {
        mDirtyRect.left = Math.min(mLastTouchX, eventX);
        mDirtyRect.right = Math.max(mLastTouchX, eventX);
        mDirtyRect.top = Math.min(mLastTouchY, eventY);
        mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
    }  
    private static final float STROKE_WIDTH = 5f;
    private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    private float mLastTouchX = 0;
    private float mLastTouchY = 0;
    private int mViewWidth = 0;
    private int mViewHeight = 0;
    private final RectF mDirtyRect = new RectF();
    private Paint mPaint = new Paint();
    private Path mPath = new Path();
}
Activity:
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointCnt = event.getPointerCount( );
for( int index = 0; index < pointCnt; index++ ){
if( mDrawView.inArea( event.getX( index ) - mDrawView.getLeft( ), event.getY( index ) - mDrawView.getTop( ) ) ){
mDrawView.onTouchEvent( event );
System.out.println( "action === pointIndex " + index );
return false;
}
} //mDrawView.setInView( false );
return super.onTouchEvent(event);
}
DrawView的inArea方法为核心方法。经过上面的处理后,可以实现当手掌放在手写区域外时在手写区域手写的功能。效果例如以下图所看到的。但你会发现当在手写区域手写的手指抬起再放下继续手写时,会直接画一条直线。通过接下来对android事件机制中ACTION_DOWN和ACTION_UP消息的分析,该问题将会得到解决。
2、android消息事件中ACTION_DOWN和ACTION_UP的触发时机即改善方案:
通过实际測试发现,android消息事件中ACTION_DOWN和ACTION_UP的触发时机分别为:
ACTION_DOWN:仅仅要有手指接触屏幕即会触发;
ACTION_UP:当屏幕上没有不论什么触控操作时触发;
对于多点触控。当某个手指抬起或者松开时会分别触发:ACTION_POINTER_DOWN和ACTION_POINTER_UP,所以对于上面遇到的问题,是因为在多点触控的情况下,仅仅点下或者松开某一根手指时,这两个消息不会触发导致,将ACTION_POINTER_DOWN和ACTION_POINTER_UP这两类消息在DrawView的onTouchEvent方法中一并处理就可以解决,改善后的view代码是这样子的:
public class DrawView extends View {
	public DrawView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaintView( );
	}
	public DrawView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView( );
	}
	public DrawView(Context context) {
		super(context);
		initPaintView( );
	}
	public void clear() {
        if (null != mPath) {
            mPath.reset();
            invalidate();
        }
    }  
    private void initPaintView() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(5f);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	mViewWidth = MeasureSpec.getSize(widthMeasureSpec);   //获取ViewGroup宽度
    	mViewHeight = MeasureSpec.getSize(heightMeasureSpec);  //获取ViewGroup高度
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mPath, mPaint);
    }  
    public boolean inArea( float x, float y ){
    	return ( x >= 0 && x <= mViewWidth && y >= 0 && y <= mViewHeight)?
true:false;
    }
	@Override
    public boolean onTouchEvent(MotionEvent event) {
    	float eventX = -1;
        float eventY = -1;
        int pointId = 0;
    	int pointCnt = event.getPointerCount( );
    	for( int index = 0; index < pointCnt; index++ ){
    		if( inArea( event.getX( index ) - getLeft( ), event.getY( index ) - getTop( ) ) ){
    			pointId = index;
    			eventX = event.getX( index ) - getLeft( );
    			eventY = event.getY( index ) - getTop( );
    			break;
    		}
    	}
    	if( ( eventX == -1 || eventY == -1 ) || ( eventX == 0 || eventY == 0 ) ){
    		return false;
    	}
        switch (event.getAction()) {
        case MotionEvent.ACTION_POINTER_1_DOWN:
        case MotionEvent.ACTION_POINTER_2_DOWN:
        case MotionEvent.ACTION_POINTER_3_DOWN:
        case MotionEvent.ACTION_DOWN: {
            mPath.moveTo(eventX, eventY);
            mLastTouchX = eventX;
            mLastTouchY = eventY;
            invalidate( );
        }
            return true;
        case MotionEvent.ACTION_MOVE:{
        	drawView( event, eventX, eventY, pointId );
        }
        break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_1_UP:
        case MotionEvent.ACTION_POINTER_2_UP:
        case MotionEvent.ACTION_POINTER_3_UP:{
        	drawView( event, eventX, eventY, pointId );
        }
            break;
        default:
            return false;
        }  
        return true;
    }
    private void drawView( MotionEvent event, float eventX, float eventY, int pointId ){
    	resetDirtyRect(eventX, eventY);
        int historySize = event.getHistorySize();
        for (int i = 0; i < historySize; i++) {
            float historicalX = event.getHistoricalX(pointId,i) - getLeft( );
            float historicalY = event.getHistoricalY(pointId,i) - getTop( );
            getDirtyRect(historicalX, historicalY);
            mPath.lineTo(historicalX, historicalY);
        }  
        mPath.lineTo(eventX, eventY);
        invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.top - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.right + HALF_STROKE_WIDTH),
                (int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));
        mLastTouchX = eventX;
        mLastTouchY = eventY;
    }
    private void getDirtyRect(float historicalX, float historicalY) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX;
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX;
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY;
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY;
        }
    }  
    private void resetDirtyRect(float eventX, float eventY) {
        mDirtyRect.left = Math.min(mLastTouchX, eventX);
        mDirtyRect.right = Math.max(mLastTouchX, eventX);
        mDirtyRect.top = Math.min(mLastTouchY, eventY);
        mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
    }  
    private static final float STROKE_WIDTH = 5f;
    private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    private float mLastTouchX = 0;
    private float mLastTouchY = 0;
    private int mViewWidth = 0;
    private int mViewHeight = 0;
    private final RectF mDirtyRect = new RectF();
    private Paint mPaint = new Paint();
    private Path mPath = new Path();
}
效果也算比較理想了:
3、边界问题处理:
经过上面两步操作,基本上可以实现手掌抑制功能了,但经过细致測试会发现,当多点触控屏幕时。某根手指从手写控件外移动到手写控件内时,会在手写区域边界直接绘制成直线的现象,例如以下图:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZWtldXk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
这是由于在android中一个完整的消息流程离不开ACTION_DOWN和ACTION_UP,当手指从手写区域外移动到手写区域内时,手写区域根本没有接收到ACTION_DOWN消息,针对这样的情况,我们须要在activity中对view做特殊处理。即当检測到有手指在手写区域但没有触发ACTION_DOWN消息时,在ACTION_MOVE消息中处理ACTION_DOWN消息应该处理的事情。
改善后的代码是这个样子的:
DrawView:
public class DrawView extends View {
	public DrawView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaintView( );
	}
	public DrawView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView( );
	}
	public DrawView(Context context) {
		super(context);
		initPaintView( );
	}
	public void clear() {
        if (null != mPath) {
            mPath.reset();
            invalidate();
        }
    }  
    private void initPaintView() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(5f);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	mViewWidth = MeasureSpec.getSize(widthMeasureSpec);   //获取ViewGroup宽度
    	mViewHeight = MeasureSpec.getSize(heightMeasureSpec);  //获取ViewGroup高度
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mPath, mPaint);
    }  
    public boolean inArea( float x, float y ){
    	return ( x >= 0 && x <= mViewWidth && y >= 0 && y <= mViewHeight)?
true:false;
    }
    @SuppressWarnings("deprecation")
	@Override
    public boolean onTouchEvent(MotionEvent event) {
    	float eventX = -1;
        float eventY = -1;
        int pointId = 0;
    	int pointCnt = event.getPointerCount( );
    	for( int index = 0; index < pointCnt; index++ ){
    		if( inArea( event.getX( index ) - getLeft( ), event.getY( index ) - getTop( ) ) ){
    			pointId = index;
    			eventX = event.getX( index ) - getLeft( );
    			eventY = event.getY( index ) - getTop( );
    			break;
    		}
    	}
    	if( ( eventX == -1 || eventY == -1 ) || ( eventX == 0 || eventY == 0 ) ){
    		return false;
    	}
        switch (event.getAction()) {
        case MotionEvent.ACTION_POINTER_1_DOWN:
        case MotionEvent.ACTION_POINTER_2_DOWN:
        case MotionEvent.ACTION_POINTER_3_DOWN:
        case MotionEvent.ACTION_DOWN: {
            mPath.moveTo(eventX, eventY);
            mLastTouchX = eventX;
            mLastTouchY = eventY;
            mInView = true;
            invalidate( );
        }
            return true;
        case MotionEvent.ACTION_MOVE:{
        	if( !mInView ){
        		mInView = true;
        		mLastTouchX = eventX;
                mLastTouchY = eventY;
                mPath.moveTo(eventX, eventY);
        	}
        	drawView( event, eventX, eventY, pointId );
        }
        break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_1_UP:
        case MotionEvent.ACTION_POINTER_2_UP:
        case MotionEvent.ACTION_POINTER_3_UP:{
        	drawView( event, eventX, eventY, pointId );
        }
            break;
        default:
            return false;
        }  
        return true;
    }
    public void setInView( boolean inView ){
    	mInView = inView;
    }
    private void drawView( MotionEvent event, float eventX, float eventY, int pointId ){
    	resetDirtyRect(eventX, eventY);
        int historySize = event.getHistorySize();
        for (int i = 0; i < historySize; i++) {
            float historicalX = event.getHistoricalX(pointId,i) - getLeft( );
            float historicalY = event.getHistoricalY(pointId,i) - getTop( );
            getDirtyRect(historicalX, historicalY);
            mPath.lineTo(historicalX, historicalY);
        }  
        mPath.lineTo(eventX, eventY);
        invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.top - HALF_STROKE_WIDTH),
                (int) (mDirtyRect.right + HALF_STROKE_WIDTH),
                (int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));
        mLastTouchX = eventX;
        mLastTouchY = eventY;
    }
    private void getDirtyRect(float historicalX, float historicalY) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX;
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX;
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY;
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY;
        }
    }  
    private void resetDirtyRect(float eventX, float eventY) {
        mDirtyRect.left = Math.min(mLastTouchX, eventX);
        mDirtyRect.right = Math.max(mLastTouchX, eventX);
        mDirtyRect.top = Math.min(mLastTouchY, eventY);
        mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
    }  
    private static final float STROKE_WIDTH = 5f;
    private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    private float mLastTouchX = 0;
    private float mLastTouchY = 0;
    private boolean mInView = false;
    private int mViewWidth = 0;
    private int mViewHeight = 0;
    private final RectF mDirtyRect = new RectF();
    private Paint mPaint = new Paint();
    private Path mPath = new Path();
}
activity也须要添加一句话:
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointCnt = event.getPointerCount( );
for( int index = 0; index < pointCnt; index++ ){
if( mDrawView.inArea( event.getX( index ) - mDrawView.getLeft( ), event.getY( index ) - mDrawView.getTop( ) ) ){
mDrawView.onTouchEvent( event );
System.out.println( "action === pointIndex " + index );
return false;
}
} mDrawView.setInView( false );
return super.onTouchEvent(event);
}
最后最终实现了我们想要的效果:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZWtldXk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
四、參考文档:
五、代码:
最后附上本文的代码:Android手掌抑制demo
Android手掌抑制功能的实现的更多相关文章
- Android Studio调试功能使用总结【转】
		Android Studio调试功能使用总结[转] 这段时间一直在使用Intellij IDEA, 今天把调试区工具的使用方法记录于此. 先编译好要调试的程序. 1.设置断点 选定要设置断点的代码 ... 
- Android 实现闹钟功能
		原文地址:Android 实现闹钟功能作者:Android_Learners 一.手机闹钟主要用到了AlarmManager类,AlarmManager类提供了访问系统定时服务的途径,开发人员可以 ... 
- Android Studio调试功能使用总结---转
		Android Studio调试功能使用总结[转] 这段时间一直在使用Intellij IDEA, 今天把调试区工具的使用方法记录于此. 先编译好要调试的程序. 1.设置断点 选定要设置断点的代码 ... 
- 集成Android免费语音合成功能(在线、离线、离在线融合)
		集成Android免费语音合成功能(在线.离线.离在线融合),有这一篇文章就够了(离线)集成Android免费语音合成功能(在线.离线.离在线融合),有这一篇文章就够了(离在线融合) 转眼间,大半年没 ... 
- Android中选项卡功能的实现
		Android中选项卡功能的实现 Android中使用TabHost和TabWidget来实现选项卡功能.TabHost必须是布局的根节点,它包含两个子节点: TabWidget,显示选项卡: Fra ... 
- Android社会化分享功能的实现步骤
		众所周知,互联网是一个资源共享的地方,在网络上,我们可以分享我们所有认为好的资源.而随着互联网信息爆发式的增长,我们习惯了一键分享功能,比如:微博分享.微信分享.QQ空间分享.人人网分享等等.由此可见 ... 
- Android微信分享功能实例+demo
		Android微信分享功能实例 1 微信开放平台注册 2 获得appId,添加到程序中,并运行程序 3 使用应用签名apk生成签名,添加到微信开放平台应用签名,完成注册 4 测试分享功能. 有问题请留 ... 
- (转载) Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框
		Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框 标签: Android清除功能EditText仿IOS的输入框 2013-09-04 17:33 70865人阅读 ... 
- Android 调用摄像头功能【拍照与视频】
		版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/ma_hoking/article/details/28292973 应用场景: 在Android开发 ... 
随机推荐
- shell命令行混合进制计算器smartbc
			需要简单的计算的时候,不想用GUI的计算器,能在shell下直接计算就最好了 查了下,有个东西叫 bc, 具体的使用就不赘述了,可以运行bc,然后进去计算,也可以echo传递过去,大概是像这样 ec ... 
- measure time program
			#include <time.h> int delay(int time) { int i,j; for(i =0;i<time;i++) for(j=0;j<10000;j+ ... 
- Long类型的坑,第一次遇见
			注意下面标红的code,如果不加这段代码会有问题,原因是一个比较的是地址,一个比较的是value,会导致不同,程序逻辑出错!加上后比较的就是值!!! allViewMap.put("proj ... 
- 【转】mybatis循环map的一些技巧
			原文地址:http://blog.csdn.net/linminqin/article/details/39154133 循环key: <foreach collection="con ... 
- JVM CMS 常用参数配置(修订)
			搜集到的一些参数内容,比较有用,大部分转载自并发编程网ifeve.com. -XX:+UseConcMarkSweepGC该标志首先是激活CMS收集器.默认HotSpot JVM使用的是并行收集器. ... 
- Python 进阶 之 yield
			.转载自:https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/ Python yield 使用浅析: 您可能听说过, ... 
- Centos 7.3 安装 Mongodb
			通过yum 安装: yum install -y mongodb-server Mongodb操作命令: #启动 systemctl start mongod.service #关闭 systemct ... 
- 打印sql语句方法
			var_dump($this->blackpool_model->getLastSql()); 
- React Native - 1 Windows下的环境配置(Windows+Android)
			参考:https://facebook.github.io/react-native/docs/getting-started.html(要FQ) 网站上建议使用Chocolatey去配环境, ... 
- #!bin/sh是啥
			第一句的#!是对脚本的解释器程序路径,脚本的内容是由解释器解释的,我们可以用各种各样的解释器来写对应的脚本,比如说/bin/csh脚本,/bin/perl脚本,/bin/awk脚本,/bin/sed脚 ... 
