Android触摸事件传递机制
作者:陈哲生
前言:在Android开发中,经常会遇到触摸事件冲突,比如ViewPager的轮播图跟Fragment的划动事件冲突,或者轮播图跟下拉事件冲突,自定义view的事件处理等,本文章将会详细介绍Activity、View、ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发、拦截、消费。
本文章将会详细介绍Activity、View、ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发、拦截、消费。
一.触摸事件的类型
触摸事件对应的是 MotionEvent 类,事件类型主要有三种:
- ACTION_DOWN:用户按下操作,表示一次触摸事件的开始。
- ACTION_MOVE:在按下的情况下,进行移动。轻微的移动都会传递到该事件。
- ACTION_UP:用户手指离开屏幕,表示一次触摸事件的
注 :如果用户仅仅的是点击而已,则只会执行到 ACTION_DOWN 和 ACTION_UP 两个事件,不会执行到 ACTION_MOVE 事件。所以 ACTION_DOWN 和 ACTION_UP 是事件是必须的。
二.触摸事件的传递阶段
1.分发(Dispatch)
在Android系统中所有的触摸事件都是由 dispatchTouchEvent 方法进行分发的。该方法中判断事件是被消费( return true ),还是继续分发给子视图处理( return super.dispatchTouchEvent ),如果当前视图是ViewGroup或者其子类,则会调用 onInterceptTouchEvent 判断是否截拦。
@Override public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
2.截拦(Intercept)
事件的截拦 InterceptTouchEvent 只存在于ViewGroup及其子类,activity和View是不存在该方法。该方法判断事件是被截拦 ( return true )并交给自身的 OnToucEvent 方法进行消费,还是继续传递给子视图( return super.InterceptTouchEvent 或者 return false )。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); }
3.消费(Consume)
事件的消费通过 OnTouchEvent 方法判断,是被消费( return true ),还是不处理( return false )并将事件传递给父视图的 OnTouchEvent 方法进行处理。
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
所有拥有事件传递能力的类:
Activity: 拥有dispatchTouchEvent 、OnTouchEvent
ViewGroup: 拥有dispatchTouchEvent 、OnInterceptTouchEvent 、OnTouchEvent
View:拥有dispatchTouchEvent 、OnTouchEvent
三、View的事件传递机制
3.1 dome
虽然说ViewGroup是View的子类,但是这是说的View指的是除ViewGroup之外的View控件子类,首先定义一个MyTextView继承TextView,打印每次事件的触发以变了解事件传递的流程。
MyTextView 类
public class MyTextView extends TextView { private 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_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
定义一个MainActivity来展现这个MyTextView,同时设置点击(onClick)和触摸(onTouch)监听。 MainActivity 类
public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{ private MyTextView mMyTextView; private String tag = "MainActiviy"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMyTextView = findViewById(R.id.text_view); // 点击监听 mMyTextView.setOnClickListener(this); // 触碰监听 mMyTextView.setOnTouchListener(this); } // MyTextView 点击事件 @Override public void onClick(View view) { switch (view.getId()){ case R.id.text_view: Log.i(tag, "MyTextView onClick"); break; } } // MyTextView 触碰事件 @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "MyTextView onTouch ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "MyTextView onTouch ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "MyTextView onTouch ACTION_DOWN"); break; } return false; } // Activity 的事件分发 @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(ev); } // Activity 的事件消费 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
3.2 打印日志
运行后,点击Text View反馈的打印日志
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
03-28 08:05:15.044 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick
dispatchTouchEvent 、 OnTouchEvent 这两个方法的返回值存在三种情况:
- 直接返回true。
- 直接返回false。
- 返回父类同名方法,super.dispatchTouchEvent 或者 super.OnTouchEvent。
由于拥有不同的返回值,所以事件传递流程也有不同,经过不断修改返回值测试,最终得到了点击事件的流程图,ACTION_DOWN 和 ACTION_UP 事件的传递流程是相同的。
3.3 事件传递流程图
从上面的流程图可以得出结论:
- 触摸事件是从 dispatchTouchEvent 开始的,默认返回父类同名方法 super ,事件将会依照嵌套层次从外向内传递( MainActivity 到 MyTextView ),到达最内层的 View 时,将由 View 的 OnTouchEvent 方法处理,该方法返回 true 时进行消费不再传递,返回 false 时再由内向外传递,由外层的 OnTouchEvent 处理。
- 如果外层向内层传递过程中,人为干扰返回 true 消费,则不会继续继续像内部传递。
- View 的事件控制顺序先执行 onTouch 再执行 onClick ,如果 onTouch 返回 true 消费,则不会继续传递,也不会执行 onClick 方法。
四、ViewGroup的事件传递机制
4.1 dome
ViewGroup是 View 的控件容器存在,拥有 dispatchTouchEvent 、 onInterceptTouchEvent 和 onTouchEvent 三个方法,比 View 多了一个 onInterceptTouchEvent 方法。为了更好的观察,我们需要自定义 MyRelativeLayout 继承 RelativeLayout 。
MyRelativeLayout类
public class MyRelativeLayout extends RelativeLayout { private final static String tag = "MyRelativeLayout"; public MyRelativeLayout(Context context) { super(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onInterceptTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onInterceptTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onInterceptTouchEvent ACTION_DOWN"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
main_activity.xml 文件
<?xml version="1.0" encoding="utf-8"?> <com.mvp.chenzhesheng.androidadvance.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.mvp.chenzhesheng.androidadvance.MyTextView android:id="@+id/text_view" android:clickable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" </com.mvp.chenzhesheng.androidadvance.MyRelativeLayout>
4.2 打印日志
04-02 08:47:57.980 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
04-02 08:47:58.260 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick
可以看到 MainActivity 和 MyTextView 的事件传递处理中添加了一层 MyRelativeLayout 。通过不同返回值测试,得到一套流程图。
4.3 流程图
从上面的流程图可以得出结论:
- 触摸事件传递是从 Activity 传递到 ViewGroup ,再传递到 View 。如果中间没有 ViewGroup 则直接从 Activity 传递到 View 。
- ViewGroup 通过 onInterceptTouchEvent 方法对事件进行截拦,如果返回 false 或者 super.onInterceptTouchEvent ,则事件会继续传递给子 View 。
- 子 View 中对事件进行消费后, ViewGroup 将不会接收到任何事件。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。