一、在View里,有两个回调函数
public boolean dispatchTouchEvent(MotionEvent ev); public boolean onTouchEvent(MotionEvent ev);
二、在ViewGroup里,有三个回调函数
public boolean dispatchTouchEvent(MotionEvent ev); public boolean onInterceptTouchEvent(MotionEvent ev); public boolean onTouchEvent(MotionEvent ev);
三、在Activity里,有两个回调函数
public boolean dispatchTouchEvent(MotionEvent ev); public boolean onTouchEvent(MotionEvent ev);
在这三个不同的地方,它们对Touch事件的处理流程很相似,但也有不同的地方。
在本节,就先研究View对Touch的处理过程。首先,Touch事件先到达dispatchTouchEvent(),我们来看看View.dispatchTouchEvent()的源码,这里面涉及到View的onTouchListener,和onTouchEvent()。
public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
从源码中,我们可以看出View的onTouchListener和onTouchEvent都是在这里被调用的。如果View的touchListener返回true,dispatchTouchEvent()直接就返回,连onTouchEvent都不会被调用了。只有View没有设置onTouchListener,或者touchListener.onTouch()返回false,才会调用onTouchEvent()。
我们还可以看出,如果onTouchEvent()被执行了的话,dispatchTouchEvent()的返回值就是onTouchEvent()的返回值。事实上,真正起作用的也就是dispatchTouchEvent(),onTouchEvent()只是被dispatchTouchEvent()调用了而已。关于这个返回值的作用,请往下看。
我们需要一些实验,自定义控件还使用之前的画板
package com.ipjmc.vgdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class CustomView extends View { private static final String TAG = "CustomView"; private int mLastX, mLastY; private int mCurrX, mCurrY; private Bitmap mBitmap; private Paint mPaint; public CustomView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setStrokeWidth(6); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); if (mBitmap == null) { mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); } Canvas tmpCanvas = new Canvas(mBitmap); tmpCanvas.drawLine(mLastX, mLastY, mCurrX, mCurrY, mPaint); canvas.drawBitmap(mBitmap, 0, 0, mPaint); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Utils.log(TAG, "dispatchTouchEvent", event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { mLastX = mCurrX; mLastY = mCurrY; mCurrX = (int) event.getX(); mCurrY = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = mCurrX; mLastY = mCurrY; break; default: break; } invalidate(); return true; } }
我们在画板上画一条线,看一下日志
如果我们把dispatchTouchEvent的返回值改为true,无论onTouchEvent()的返回值是什么,都不影响。
现在,我们把dispatchTouchEvent的返回值,改为false看看会怎么样
@Override public boolean dispatchTouchEvent(MotionEvent event) { Utils.log(TAG, "dispatchTouchEvent", event.getAction()); super.dispatchTouchEvent(event); return false; }
我们在画板上画一条线,看一下日志
我们可以看到,只在Touch事件是ACTION_DOWN的时候,打印了一条日志,而且画板上也没有画出一条线。这是说明在ACTION_DOWN的时候,如果dispatchTouchEvent返回false,那么这个View就接收不到后面的触屏事件了。
如果我们这样改呢?dispatchTouchEvent()只在ACTION_DOWN的时候,返回true,其他时候返回false。答案是一切又恢复正常了。
public boolean dispatchTouchEvent(MotionEvent event) { Utils.log(TAG, "dispatchTouchEvent", event.getAction()); super.dispatchTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_DOWN) { return true; } return false; }
可以这样理解。每一个触屏事件都必须是以ACTION_DOWN作为开头,后面跟一系列的ACTION_MOVE,最后再有一个ACTION_UP(或ACTION_CANCEL),标识触屏事件结束。所以Android就在ACTION_DOWN的时候做文章,官方文档对dispatchTouchEvent的返回值的解释是:True if the event was handled by the view, false otherwise。我们可以简单的理解为如果返回true,就说明它需要处理这个事件,就让它接收所有的触屏事件,否则,说明它不用处理,也就不让它接收后续的触屏事件了。