1、引子
由于android是采用分层布局(可以想象成PS时的图层概念一样),这样才可以在有限大小的手机屏幕上完成一些复杂的操作。当手指点击屏幕开始,这些动作在各层之间如何传递?就引出了Android的事件分发机制。之所以称为事件,是由于在Android中将所有在屏幕的动作封装成3个事件
ACTION_DOWN:手指按下
ACTION_MOVE:手指在屏幕滑动
ACTION_UP:手指从屏幕抬起
每次都是从ACTION_DOWN开始,到ACTION_UP结束,中间伴随着ACTION_MOVE;有了事件就对应着事件处理3个重要的方法
【1】事件分发dispatchTouchEvent(MotionEvent ev)
【2】事件拦截 onInterceptTouchEvent(MotionEvent ev)
【3】事件响应 onTouchEvent(MotionEvent ev)
初始情况返回值都是False,表示自身未处理需要继续流程
其中ViewGroup以及继承ViewGroup的容器控件如布局文件RelativeLayout等,需回调这三个方法;通常View则只回调【1】【3】,对应一些显示控件如button等。
三者之间的关系如下(以ViewGroup为例):
这里一定要注意的是调用自身的dispatchTouchEvent后若继续让后面view处理,则再调用后面view的dispatchTouchEvent,直到哪个控件开始处理则会调用对应的onTouchEvent方法,这在源码中可以看出。
2、生活中的实例
先来感性认识一下:
Activity——部门boss
MyViewGroup(重写的RelativeLayout——ViewGroup容器控件)——项目组boss
MyButton(重写的Button控件——view控件)——屌丝程序员
正常情况下
事件传递的顺序:(Activity—Window(ViewGroup布局)——View)
Activity(部门boss)——>MyViewGroup(项目组boss)——>重写的MyButton(屌丝程序员)
事件处理的顺序:
重写的MyButton(屌丝程序员)——>MyViewGroup(项目组boss)——>Activity(部门boss)
事件传递的返回值布尔类型 True 表示拦截在拦截处消化无需上传(下发) False不拦截继续流程
事件处理的返回值布尔类型 True 已处理无需上级审核 False未处理需要上级审核
情形一:正常流程
因此对于上面的例子正常情况下层层处理流程如下
三大函数默认都是返回false,所以可以不拦截,直接分发上传,走完整个流程。可以形象解释为:部门boss把任务指派给项目组boss,项目组boss再指派给程序员,然后程序员搞定了汇报给他的上级项目组boss,然后由项目组汇报给部门boss
情形2:遇到好心项目组boss
要是某一步处理返回true表示本层已经处理完无需下发,比如MyViewGroup的 onInterceptTouchEvent 返回true,则流程如下
解释为:好比部门boss把任务下发给项目组boss,他觉得比较简单,就自己搞定了,然后告诉了部门boss
3、示例代码
重写其3个事件处理函数,函数方法内内打上LOG便于观察
关键代码如下,Mybutton重写【1】【3】同样是在函数内部打上Log
MyViewGroup继承RelativeLayout,(Mybutton继承Button控件)
<span style="font-size:18px;">/** * Created by ELVIS on 2015/10/18. * */ public class MyViewGroup extends RelativeLayout{ private final static String TAG = "MyViewGroup"; public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_UP"); break; } // return super.dispatchTouchEvent(ev); return true; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_UP"); break; } return super.onTouchEvent(ev); } }</span>
MyBotton.java
/** * Created by ELVIS on 2015/10/18. * 自定义测试BUtton */ public class MyButton extends Button { public static final String TAG = "MyButton"; public MyButton(Context context, AttributeSet attrs) { super(context, attrs); } public MyButton(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG, "MyButton dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"MyButton dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"MyButton dispatchTouchEvent--ACTION_UP"); break; } return super.dispatchTouchEvent(ev); // return true; } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"MyButton onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"MyButton onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"MyButton onTouchEvent--ACTION_UP"); break; } return super.onTouchEvent(ev); //return true; } }
布局文件使用自己的控件,只是在自定义布局文件中放了一个BUtton,如下
<span style="font-size:18px;"><com.example.elvis.mypaintest.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <com.example.elvis.mypaintest.MyButton android:id="@+id/myBt" android:text="button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="193dp" android:gravity="center_horizontal" /> </com.example.elvis.mypaintest.MyViewGroup></span>
此时运行,点击button按钮看到对应的LOG
情形一
这个属于最基本流程,对应情形一,执行流程是首先由activity捕获到ACTION_DWON事件,然后调用activity的dispatchTouchEvent,接着绕开activity的onTouchEvent直接将事件传递给MyViewGroup,由于它也未消耗该事件,因此也绕开调用onTouchEvent,直到MyButton的dispatchTouchEvent,在之后调用该控件的onTouchEvent,ACTION_UP事件也是一样的流程。
情形二:
将MyGroup的dispatchTouchEvent返回为 true,表示消耗了该事件,此时绕开了mybutton直接传给了MainActivity',ACTION_UP也是一样的流程
4、总结
【事件分发】:public boolean dispatchTouchEvent(MotionEvent ev)
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
* 如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
* 如果 return false,事件分发分为两种情况:
1. 如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
2. 如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。
如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。
【事件拦截】:public boolean onInterceptTouchEvent(MotionEvent ev)
在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:
* 如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
* 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
* 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),处理逻辑与返回false相同。
【事件响应】:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
* 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
* 如果返回了 true 则会接收并消费该事件。
* 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。