前言
这是demo 链接
Android 中 View 的事件传递是一个老生常谈的问题,但也是学习 Android 的重点和难点。本人将分三篇来分享 Android 中的事件传递。
- 通过 demo 分析 Android 事件的传递过程
- 通过源码分析 Android 事件的传递过程
- 运用所学解决 Android 中滑动冲突
MotionEvent
Android 中事件的传递其实传递的就是 MotionEvent 对象。MotionEvent 中封装了事件的各种状态和属性。最常用的最典型的有如下几种:
- ACTION_DOWN ———————手指刚接触屏幕
- ACTION_MOVE ———————手指在屏幕上移动
- ACTION_UP ———————手指从屏幕上松开的一瞬间
Android 事件传递相关方法
- public boolean dispatchTouchEvent(MotionEvent ev) 负责事件的分发
- public boolean onInterceptTouchEvent(MotionEvent ev) 负责事件拦截
- public boolean onTouchEvent(MotionEvent event) 负责事件的处理
在 ViewGroup 中有上述三个方法,但是在 View 中没有 onInterceptTouchEvent 方法。因为 View 没有子 View,所以就不牵扯到事件的拦截。
编写 demo
自定义相关 View
由于 LinearLayout 继承 ViewGroup,我们只需新建类 CustomLinearLayout 继承 LinearLayout。重写上述三个方法,如下
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "======dispatchTouchEvent======ACTION_DOWN==");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "======dispatchTouchEvent======ACTION_MOVE==");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "======dispatchTouchEvent======ACTION_UP==");
break;
}
return super.dispatchTouchEvent(ev);}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "======onInterceptTouchEvent======ACTION_DOWN==");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "======onInterceptTouchEvent======ACTION_MOVE==");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "======onInterceptTouchEvent======ACTION_UP==");
break;
}
return super.onInterceptTouchEvent(ev);}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "======onTouchEvent======ACTION_DOWN==");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "======onTouchEvent======ACTION_MOVE==");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "======onTouchEvent======ACTION_UP==");
break;
}
return super.onTouchEvent(event);}
同样的新建类 CustomTextView 继承 TextView,重写 dispatchTouchEvent 和 onTouchEvent。同样重写 MainActivity 中的 dispatchTouchEvent 和 onTouchEvent。
布局文件
<com.cuifei.test.CustomLinearLayout 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.cuifei.test.CustomButton
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:text="CustomButton" />
查看运行结果
事件分发 Log
由图事件分发 Log 可以看出 ACTION_DOWN 传递的顺序示意图,如下图:
Android 事件分发示意图
在这之间我们并没有消费事件,由此可以看到事件从最外层逐级向内层传递,如果在这之间事件没有被消费掉,事件将由内层控件逐级向外层控件传递。
以 ACTION_DOWN 为例,详细分析 Android 事件传递的流程。 首先,MainActivity 捕获到 ACTION_DOWN 事件,并由 MainActivity 的 dispatchTouchEvent 方法分发给其子控件。因为 CustomLinearLayout 是 MainActivity 的子控件(我们暂且这么认为),所以 ACTION_DOWN 被分发给 CustomLinearLayout 并由 CustomLinearLayout 的 dispatchTouchEvent 继续分发。由于 CustomLinearLayout 是 ViewGroup 能够对事件进行拦截,所以 ACTION_DOWN 被分发给 CustomLinearLayout 的 onInterceptTouchEvent 方法。如果 onInterceptTouchEvent 返回 true 则拦截,否则不拦截。这里 CustomLinearLayout 的 onInterceptTouchEvent 返回 false,不对 ACTION_DOWN 拦截。所以 ACTION_DOWN 继续被分发给 CustomLinearLayout 的子控件 CustomTextView ,并由 CustomTextView 的 dispatchTouchEvent 进行分发。因为 CustomTextView 是 View 并且是最内层的 View ,所以 ACTION_DOWN 被分发给 CustomTextView 的 onTouchEvent 方法进行处理。本 demo 中 ACTION_DOWN 没有被 CustomTextView 的 onTouchEvent 处理掉,所以 ACTION_DOWN 向上传递给 CustomLinearLayout 的 onTouchEvent 。而 CustomLinearLayout 的 onTouchEvent 也没有处理 ACTION_DOWN,所以 ACTION_DOWN 继续向上传递给 MainActivity 的 onTouchEvent 。而 MainActivity 的 onTouchEvent 也没有处理,至此 ACTION_DOWN 将被释放,ACTION_DOWN 事件分发结束。
由图 事件分发 Log 我们还可以看出,ACTION_MOVE 和 ACTON_UP 并没有向内层控件传递,而是由 MainActivity 自己分发和处理。这说明 ACTION_MOVE 和 ACTON_UP 只传递到消费 ACTION_DOWN 的控件。为了验证这个我们将 CustomLinearLayout.onTouchEvent 的返回值改为 true 表示事件被 CustomLinearLayout 处理。
事件拦截Log
由 事件拦截Log可以看出 ACTION_MOVE 和 ACIONT_UP 只传递到了 CustomLinearLayout 这说明我们的上述观点是正确的。
由上面的代码可以看到和 onTouchEvent 方法一样,dispatchTouchEvent 和 onInterceptTouchEvent 方法也有一个 boolean 类型的返回值,这个返回值分别代表什么意思呢?
- onInterceptTouchEvent 返回值为 true 时;表示事件被拦截,不在向内层控件传递。反之则向内层控件传递。
- dispatchTouchEvent 返回 true 表示事件被 dispatchTouchEvent 自身处理消耗掉。至此,事件已经完结;返回 false 不再继续进行分发,并交由上层控件的onTouchEvent 方法进行处理;返回 super.dispatchTouchEvent(ev) 时,事件将继续传递。
最后两条结论的 log 将不再贴出,请读者自己验证。
最后给出本 demo 中事件分发的详细流程图,如下图:
Android 事件分发流程图