Android触摸事件传递机制
一次完整的事件传递主要包括三个阶段,分别是事件的分发、拦截和消费.
1.1 触摸事件的类型
触摸事件对应的是MotionEvent类,事件的类型主要有如下三种:
1>.ACTION_DOWN: 用户手指的按下操作,一个按下操作标志着一次触摸事件的开始.
2>.ACTION_MOVE: 用户手指的按压屏幕后,在松开之前,如果移动的距离超过一定的阈(yu)值,那么会被判定为ACTION_MOVE操作,一般情况下,手指的轻微西东都会触发一系列的移动事件.
3>.ACTION_UP: 用户手指离开屏幕的操作,一次抬起操作标志着一次触摸事件的结束.
在一次屏幕触摸的操作中,ACTION_DOWN和ACTION_UP这两个事件是必需的,而ACTION_MOVE视情况而定,如果用户仅仅是点击了一下屏幕那么可能只会监测到按下和抬起的动作.
1.2 事件传递的三个阶段
1.分发(Dispatch)
事件的分发对应着dispatchTouchEvent 方法,在Android系统中,所有的触摸事件都是通过这个方法来分发的,
public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); }
该方法返回true表示事件被当前视图消费掉,不再继续分发事件;方法返回super.dispatchTouchEvent(ev) 表示继续分发该事件.
如果当前视图是ViewGroup及其子类,则回调用onInterceptTouchEvent方法判定是否拦截该事件.
2.拦截(Intercept)
事件的拦截对应着onInterceptTouchEvent方法,这个方法只在ViewGroup及其子类才存在,在View和Activity中不存在的.
public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); }
该方法返回true表示拦截这个事件,不在继续分发给子视图,同时交由自身的onTouchEvent方法进行消费;返回false和super.onInterceptTouchEvent(ev)表示不对事件进行拦截,需要继续传给子视图.
3.消费(Consume)
事件的消费对应着onTouchEvent 方法
public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
该方法返回值为true表示当前视图可以处理对应的事件,事件将不会向上传递给父视图;返回值为false表示当前的视图不处理这个事件,事件会被传递给父视图的onTouchEvent方法进行处理.
在Android系统中,拥有事件传递处理能力的类有一下三种:
1>.Activity: 拥有dispatchTouchEvent 和 onTouchEvent 两种方法.
2>.ViewGroup: 拥有dispatchTouchEvent 、onInterceptTouchEvent和 onTouchEvent 三种方法.
3>.View: 拥有dispatchTouchEvent 和 onTouchEvent 两种方法.
1.3 View的事件传递机制
View控件拥有dispatchTouchEvent 和 onTouchEvent 两种方法,定义一个继承TextView的类和MyTextView,如下所示:我们将每个事件的触发事件都打上Log,以方便了解事件的传递流程.
public class MyTextView extends TextView { private static final String TAG ="MyTextView"; public MyTextView(Context context) { super(context); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG, "dispatchTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "dispatchTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "dispatchTouchEvent: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "dispatchTouchEvent: ACTION_CANCEL"); break; default: break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouchEvent: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "onTouchEvent: ACTION_CANCEL"); break; default: break; } return super.onTouchEvent(event); } }
同时定义一个MainActivity用来展示MyTextView,在这个Activity中,为MyTextView设置了点击(onClick)和触摸(onTouch)监听,方便跟踪了解事件传递的流程:
public class MainActivity extends AppCompatActivity implements View.OnClickListener , View.OnTouchListener { private static final String TAG ="MainActivity"; private MyTextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView =findViewById(R.id.my_text); mTextView.setOnClickListener(this); mTextView.setOnTouchListener(this); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG, "dispatchTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "dispatchTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "dispatchTouchEvent: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "dispatchTouchEvent: ACTION_CANCEL"); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouch(View v, MotionEvent event) { switch (v.getId()) { case R.id.my_text: switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "MyTextView onTouch: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "MyTextView onTouch: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "MyTextView onTouch: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "MyTextView onTouch: ACTION_CANCEL"); break; default: break; } break; default: break; } return false; } @Override public void onClick(View v) { switch (v.getId()){ case R.id.my_text: Log.e(TAG, "MyTextView onClick: "); break; default: break; } } }
运行上面的代码,点击MyTextView,在Logcat中将打印如下日志:
09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_DOWN 09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_DOWN 09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_DOWN 09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_DOWN 09-02 20:25:01.833 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onClick:
按下按压抬起MyTextView,在Logcat中将打印如下日志:
09-02 20:33:50.583 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_DOWN 09-02 20:33:50.583 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_DOWN 09-02 20:33:50.583 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_DOWN 09-02 20:33:50.583 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_DOWN 09-02 20:33:50.643 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_MOVE 09-02 20:33:50.643 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_MOVE 09-02 20:33:50.643 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_MOVE 09-02 20:33:50.643 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_MOVE 09-02 20:33:50.673 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_MOVE 09-02 20:33:50.673 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_MOVE 09-02 20:33:50.673 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_MOVE 09-02 20:33:50.673 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_MOVE 09-02 20:33:51.173 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_MOVE 09-02 20:33:51.173 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_MOVE 09-02 20:33:51.173 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_MOVE 09-02 20:33:51.173 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_MOVE 09-02 20:33:51.173 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_UP 09-02 20:33:51.183 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_UP 09-02 20:33:51.183 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_UP 09-02 20:33:51.183 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_UP 09-02 20:33:51.183 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onClick:
从上面的运行的日志可以看出,dispatchTouchEvent、onTouchEvent这两个方法的返回值存在三种情况:
1>. return false;
09-02 20:46:38.013 21590-21590/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_DOWN
09-02 20:46:38.103 21590-21590/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_UP
2>. return true;
09-02 20:45:23.733 20676-20676/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_DOWN
09-02 20:45:23.753 20676-20676/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_UP
3>. 返回父类的同名方法 return super.dispatchTouchEvent(ev);
09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_DOWN 09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_DOWN 09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_DOWN 09-02 20:25:01.783 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_DOWN 09-02 20:25:01.833 15374-15374/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_UP 09-02 20:25:01.843 15374-15374/com.kitking.androidtest E/MainActivity: MyTextView onClick:
不同的返回值会导致事件的传递流程相差甚远,通过不断的修改这些方法的返回值查看日志的记录;可以得出以下结论:
1>. 触摸事件的传递流程是从dispatchTouchEvent开始的,如果不进行人为干预(也就是说默认返回父类的同名函数),则事件将会依照嵌套层向外层传递,到达最内层的View时,就由它的onTouchEvent 方法处理,
则返回的true,如果处理不了,怎返回false,这时事件会重新向外层传递,并由外层的View的onTouchEvent方法进行处理,以此类推.
2>. 如果事件在向内层传递过程中由于人为干预,事件处理函数返回true,则会导致事件提前被消费,内层View将不会收到这个事件.
3>. View控件的事件触发顺序是先执行onTouch 方法,在最后才执行 onClick方法. 如果onTouch 返回true,则事件不会继续传递,最后也不会调用onClick方法;如果onTouch返回false,则事件继续传递.
1.4. ViewGroup的事件传递机制
ViewGroup拥有dispatchTouchEvent 、onInterceptTouchEvent和 onTouchEvent 三种方法.
可以看出和View的唯一的区别是多了一个onInterceptTouchEvent方法;演示,我们定义如下一个ViewGroup,继承RelativeLayout ,实现一个MyRelativeLayout .
public class MyRelativeLayout extends RelativeLayout { private static final String TAG ="MyRelativeLayout"; public MyRelativeLayout(Context context) { super(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG, "dispatchTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "dispatchTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "dispatchTouchEvent: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "dispatchTouchEvent: ACTION_CANCEL"); break; default: break; } return super.dispatchTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG, "onInterceptTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onInterceptTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onInterceptTouchEvent: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "onInterceptTouchEvent: ACTION_CANCEL"); break; default: break; } return super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouchEvent: ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.e(TAG, "onTouchEvent: ACTION_CANCEL"); break; default: break; } return super.onTouchEvent(event); } }
<?xml version="1.0" encoding="utf-8"?> <com.kitking.androidtest.event.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.kitking.androidtest.event.MyTextView android:id="@+id/my_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30dp" android:text="Event"/> </com.kitking.androidtest.event.MyRelativeLayout>
运行,点击MyTextView,在Logcat中打印日志如下:
09-02 21:01:07.813 25884-25884/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_DOWN 09-02 21:01:07.823 25884-25884/com.kitking.androidtest E/MyRelativeLayout: dispatchTouchEvent: ACTION_DOWN 09-02 21:01:07.823 25884-25884/com.kitking.androidtest E/MyRelativeLayout: onInterceptTouchEvent: ACTION_DOWN 09-02 21:01:07.823 25884-25884/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_DOWN 09-02 21:01:07.823 25884-25884/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_DOWN 09-02 21:01:07.823 25884-25884/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_DOWN 09-02 21:01:07.833 25884-25884/com.kitking.androidtest E/MainActivity: dispatchTouchEvent: ACTION_UP 09-02 21:01:07.833 25884-25884/com.kitking.androidtest E/MyRelativeLayout: dispatchTouchEvent: ACTION_UP 09-02 21:01:07.833 25884-25884/com.kitking.androidtest E/MyRelativeLayout: onInterceptTouchEvent: ACTION_UP 09-02 21:01:07.833 25884-25884/com.kitking.androidtest E/MyTextView: dispatchTouchEvent: ACTION_UP 09-02 21:01:07.833 25884-25884/com.kitking.androidtest E/MainActivity: MyTextView onTouch: ACTION_UP 09-02 21:01:07.833 25884-25884/com.kitking.androidtest E/MyTextView: onTouchEvent: ACTION_UP 09-02 21:01:07.853 25884-25884/com.kitking.androidtest E/MainActivity: MyTextView onClick:
可以看到,与View的事件流程唯一不一样的地方是在MainActivity和MyTextView之间增加了MyRelativeLayout,同理通过打印日志可以获得以下结论:
1>. 触摸事件的传递顺序由Activity到ViewGroup,在由ViewGroup传递给它的子类View
2>. ViewGroup通过dispatchTouchEvent方法对事件进行拦截,如果该方法返回true,则事件不会继续传给子View,如果返回false或者super.dispatchTouchEvent,则事件会继续传递给子View.
3>. 在子View对事件进行消费后,ViewGroup将接收不到任何事件.