Android手掌抑制功能的實現

        近期需要實現一個功能,在Activity中有一個手寫區域,爲了更好的用戶體驗,需要知足即便整個手掌放在屏幕上時(android平板,屏幕比較大)也僅僅響應手寫區域內的操做,即在支持多點觸控的狀況下,僅僅響應指定的區域,我將這個功能稱做「手掌抑制」,即在手寫時。手掌放在屏幕上面不作不論什麼響應。html

        初看這個功能很是easy,依照以前處理listview、gridview裏面的子view不能響應的方式,僅僅要在activity層不攔截向手寫view傳遞的消息就能夠實現想要的效果,但通過實際測試和對android消息機制的具體研究發現。要實現這個功能會有點小複雜。java

 

1、android的消息傳遞機制:android

一、基礎知識:編程

        (1) 所有Touch事件都被封裝成了MotionEvent對象。包含Touch的位置、時間、歷史記錄以及第幾個手指(多指觸摸)等。canvas

        (2) 事件類型分爲ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每個事件都是以ACTION_DOWN開始ACTION_UP結束。ide

        (3) 對事件的處理包含三類:函數

        傳遞——dispatchTouchEvent()spa

        攔截——onInterceptTouchEvent().net

        消費——onTouchEvent()和OnTouchListenercode

 

二、傳遞流程

        (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。

 

三、實際狀況:

        能夠響應事件處理方法的控件包含:ViewGroup、View、Activity,各種控件對三個事件響應處理方法的支持狀況例如如下:

 

         這三個控件。Activity是處於最外層的,消息的傳遞首先是系統回調消息給Activity。Activity將消息傳遞給每一個ViewGroup,而後ViewGroup會將消息傳遞給對應地子View。

        本文所描寫敘述的手寫控件是一個view,在有系統消息回調時僅僅有上層控件將消息分發下來,它才能夠消費和處理這些消息。

 

2、問題現象:

        接着咱們進入正題,依照我在開篇介紹的那種處理方式,寫一個手寫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方法效果是同樣的,也就是說明這樣的處理方式是不可行的。

 

3、解決方式:

        加了贊成分發消息的方法。不攔截向下分發消息反而還不行,這個問題詳細緣由我沒有找到。我的以爲是系統針對activity的消息事件處理作了特殊處理,它的優先級是最高的,儘管可以複寫它的消息分發dispatchTouchEvent方法,但是不管是返回true仍是false結果都是屏幕不能響應不論什麼操做。這一點有知道的大拿歡迎指點指點。

        要實現本文想要的功能顯然不能使用開篇講到的方法,在研究這個問題的過程當中,發現儘管activity的dispatchTouchEvent沒法控制,但其onTouchEvent方法是有效的,僅僅要在屏幕的不論什麼一個地方操做,onTouchEvent裏面都會有打印消息,細緻回想上面提到的android消息分發機制會發現,僅僅要咱們在屏幕上操做時,模擬系統在activity的onTouchEvent方法裏面向手寫view派發消息就能夠實現想要的功能。詳細方法例如如下:

一、向下派發消息的實現以及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消息的分析,該問題將會獲得解決。


二、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(); }

效果也算比較理想了:



三、邊界問題處理:

        通過上面兩步操做,基本上能夠實現手掌抑制功能了,但通過細緻測試會發現,當多點觸控屏幕時。某根手指從手寫控件外移動到手寫控件內時,會在手寫區域邊界直接繪製成直線的現象,例如如下圖:


        這是因爲在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);
	}

最後最終實現了咱們想要的效果:



4、參考文檔:

Android Touch事件傳遞機制

Android 編程下 Touch 事件的分發和消費機制


5、代碼:

最後附上本文的代碼:Android手掌抑制demo

相關文章
相關標籤/搜索