在自定义View中,经常需要处理Android事件分发的问题,尤其在有多个输入设备(如遥控、鼠标、游戏手柄等)时,事件处理问题尤为突出。Android事件分发机制,一直以来都是一个让众多开发者困扰的难点,至少笔者在工作的前几年中,没有特意研究它之前,就经常云里雾里。实际上,该问题的“七寸”就是dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)这三个方法和MotionEvent事件实体,咱们这里索性称它们为“四大恶人”吧。本文将主要通过示例演示的方式来打这个“七寸”吧。
一、事件分发机制与生活场景
Android的事件分发机制和生活中的很多场景有着相似之处,可能Android的很多设计灵感就是来源于生活吧。
1、示例中的四个角色
在众多示例当中,有一个经常被拿来举例的经典场景就是PM(项目经理)、Team Leader、Programmer之间工作安排的问题,咱们这里也用这个场景来类比,另外再加一个Boss的角色,以便于理解。现在这个公司中四个角色职位从高到低依次为,Boss > PM > Team Leader > Programmer。
2、可能出现的场景
一般来说,一个寻常的工作流程是:Boss想到有个功能很流行,就会指示PM去办,PM会分发给Team Leader,而Team Leader会安排Proggrammer去编程实现。Boss是决策者,事件来源于他;PM和Team Leader是管理者,负责一层层把任务派发给自己的下属;Programmer是具体来做开发的,所以事件最后落在他的身上了,最后由他来完成。但是现实工作中,工作并不总是这个流程,还有很多其它场景,比如:
(1)市面上出现了一很火的行业,做智能手机。本公司是否需要也涉足这个行业,需要Boss自己开董事会来做决策。那么这个事情就是Boss应该处理的事情,他就不会派发给PM,Boss以下的员工看来,就跟没有任何事情一样。
(2)Boss确定了智能手机是一个很有前景的行业,确定了要做,于是就召集PM,做好立项工作。那这个立项工作就是这个PM的工作了,如何立项,需要招聘什么样人等各项准备工作,PM就得自己做好整个计划,事情就到他这里为止了,不会再往下派发。
(3)做手机挣了钱,Boss决定奖励一些表现优异的Programmer。这需要先安排PM,PM然后安排下面的Team Leader对下面的Programmer们做综合考量,将优秀者报上去。这就是Team Leader需要完成的工作,他也无法再传下去。
(4)如前面说到的,Boss要求做一个市面上很流行的功能,经过PM和Team Leader层层派发到了Programmer手上。虽然事情派发到了Programmer手上,但也有两种情形:
1)在Programmer能力范围内,做得很完美。这种情况事情就在这里被处理掉了,无需再传递出去了。后续Boss一系列的类似功能也会继续派发下来,也以这样的流程来走下去。
2)Programmer能力有限,做不了。这种情况下,他就需要告诉Team Leader这一情况,把这个任务再依次传递给自己的领导。这样又有两种情况:①Team Leader 或者PM本身也是研发出身,开发能力也很强,就把这个开发任务给完成了,这样事情也就到此为止不再传递了。在Boss看来,下面的团队有能力开发好这类需求,于是后续Boss一系列的类似功能也会继续派发下来。由于上一次的任务中,Team Leader或者PM知道自己的手下处理不了这类任务,所以当上一级把任务分发到自己这一层的时候,就不会再继续往下分发,而是自己亲自处完成。②Team Leader和PM都是管理出身,这个功能他们也不会做。于是就层层往上报,最后到老板那里,老板自己处理,是不了了之还是再找招人,或者自己也是个大牛程序员自己可以开发出来,这个决策由老板来做。老板知道自己下面的团队完成不了这一系列任务,后续一系列这类功能,就不再派发下去了。
上述列出了一些比较有代表性的可能情况,下面咱们根据这些情况,来理解事件的分发机制。其实这里PM和Team Leader可以整体作为一个角色来看,只是为了后面看代码和日志方便对应,才分开为两个角色的。
二、MotionEvent简介
在讲Android事件分发机制前,先简单了解一些MotionEvent,因为它就是这个“事件”。以下截取了部分源码中的描述:
1 ......
2 * <p>
3 * Motion events describe movements in terms of an action code and a set of axis values.
4 * The action code specifies the state change that occurred such as a pointer going
5 * down or up. The axis values describe the position and other movement properties.
6 * </p>
7 ......
8 public final class MotionEvent extends InputEvent implements Parcelable {
9 public static final int ACTION_DOWN = 0;
10 public static final int ACTION_UP = 1;
11 public static final int ACTION_MOVE = 2;
12 ......
13 }
MotionEvent,顾名思义,动作事件的意思。它通过一个action码和一套坐标值来描述动作。action码指定了当如指针按下或者抬起等事件发生时的状态改变,坐标值则描述了事件在屏幕中的位置和其它动作属性值。如下内容为MotionEvent的toString方法打印出来的结果:
MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=173.0, y[0]=138.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x2, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=34268429, downTime=34268429, deviceId=8, source=0x1002 }
从这里可以看到,事件发生的action,时间,坐标等很多信息。本文中暂时只关注aciton这个字段,“action=ACTION_DOWN”表示了按下事件。
平时触摸屏幕时,一个最简单的事件包括了“ACTION_DOWN”和“ACTION_UP”,“ACTION_DOWN”表示手指按下,而““ACTION_UP”表示手指抬起来,这两个action才构成了一个完整的事件。如果手指在屏幕上有移动,还会包含“ACTION_MOVE”,此时一个完整的事件就包括“ACTION_DOWN”,多个“ACTION_MOVE”,“ACTION_UP”。当然,实际工作中会有很多复杂的情况出现,可能会出现一些其它的aciton,本文为了演示的方便,只考虑“ACTION_DOWN”和“ACTION_UP”的场景。
三、示例代码
为了演示事件分发机制的工作流程,这里编写一个示例来进行演示。整个Acitivity模拟Boss角色;在其界面中的最外层模拟PM,继承自RelativeLayout,是一个父布局;PM下嵌套一层,也是一个父布局,继承自RelativeLayout,用于模拟Team Leader;最里面一层是一个叶子View,继承自Button,模拟Programmer。效果图及对应代码分别如下。
1、演示界面
2、默认场景下的代码示例
如下的代码中,需要重写的方法dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)均返回默认值,即super.xxx。平时咱们使用系统原生控件时,无法修改它们的源码,所以系统给的默认场景就是这样的。
(1)Boss:EventDemoActivity
1 public class EventDemoActivity extends AppCompatActivity {
2
3 @Override
4 protected void onCreate(Bundle savedInstanceState) {
5 super.onCreate(savedInstanceState);
6 setContentView(R.layout.activity_event_demo);
7 }
8
9 @Override
10 public boolean dispatchTouchEvent(MotionEvent ev) {
11 Log.i("songzheweiwang","[EventDemoActivity-->dispatchTouchEvent]ev="+EventUtil.parseAction(ev.getAction()));
12 return super.dispatchTouchEvent(ev);
13 }
14
15 @Override
16 public boolean onTouchEvent(MotionEvent event) {
17 Log.i("songzheweiwang","[EventDemoActivity-->onTouchEvent]event="+EventUtil.parseAction(event.getAction()));
18 return super.onTouchEvent(event);
19 }
20 }
该Activity的布局文件为
1 //==========================activity_event_demo.xml===========================
2 <?xml version="1.0" encoding="utf-8"?>
3 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent">
6
7 <com.example.demos.customviewdemo.ViewGroupOuter
8 android:layout_width="300dp"
9 android:layout_height="300dp"
10 android:layout_centerInParent="true"
11 android:background="@android:color/holo_orange_dark">
12
13 <com.example.demos.customviewdemo.ViewGroupMiddle
14 android:layout_width="200dp"
15 android:layout_height="200dp"
16 android:layout_centerInParent="true"
17 android:background="@android:color/holo_blue_dark">
18
19 <com.example.demos.customviewdemo.ViewInner
20 android:id="@+id/viewInner"
21 android:layout_width="100dp"
22 android:layout_height="100dp"
23 android:layout_centerInParent="true"
24 android:background="@android:color/holo_green_dark"/>
25 </com.example.demos.customviewdemo.ViewGroupMiddle>
26 </com.example.demos.customviewdemo.ViewGroupOuter>
27 </RelativeLayout>
(2)PM:ViewGroupOuter
1 public class ViewGroupOuter extends RelativeLayout {
2
3 public ViewGroupOuter(Context context, @Nullable AttributeSet attrs) {
4 super(context, attrs);
5 }
6
7 @Override
8 public boolean dispatchTouchEvent(MotionEvent ev) {
9 Log.i("songzheweiwang","[ViewGroupOuter-->dispatchTouchEvent]ev="+EventUtil.parseAction(ev.getAction()));
10 return super.dispatchTouchEvent(ev);
11 }
12
13 @Override
14 public boolean onInterceptTouchEvent(MotionEvent ev) {
15 Log.i("songzheweiwang","[ViewGroupOuter-->onInterceptTouchEvent]ev="+EventUtil.parseAction(ev.getAction()));
16 return super.onInterceptTouchEvent(ev);
17 }
18
19 @Override
20 public boolean onTouchEvent(MotionEvent event) {
21 Log.i("songzheweiwang","[ViewGroupOuter-->onTouchEvent]event="+EventUtil.parseAction(event.getAction()));
22 return super.onTouchEvent(event);
23 }
24 }
(3)Team Leader:ViewGroupMiddle
1 public class ViewGroupMiddle extends RelativeLayout {
2
3 public ViewGroupMiddle(Context context, @Nullable AttributeSet attrs) {
4 super(context, attrs);
5 }
6
7 @Override
8 public boolean dispatchTouchEvent(MotionEvent ev) {
9 Log.i("songzheweiwang","[ViewGroupMiddle-->dispatchTouchEvent]ev="+EventUtil.parseAction(ev.getAction()));
10 return super.dispatchTouchEvent(ev);
11 }
12
13 @Override
14 public boolean onInterceptTouchEvent(MotionEvent ev) {
15 Log.i("songzheweiwang","[ViewGroupMiddle-->onInterceptTouchEvent]ev="+EventUtil.parseAction(ev.getAction()));
16 return super.onInterceptTouchEvent(ev);
17 }
18
19 @Override
20 public boolean onTouchEvent(MotionEvent event) {
21 Log.i("songzheweiwang","[ViewGroupMiddle-->onTouchEvent]event="+EventUtil.parseAction(event.getAction()));
22 return super.onTouchEvent(event);
23 }
24 }
(4)Programmer:ViewInner
这里先以Button为例,因为Button默认是可以处理Touch事件的,也就是说,事件传到这里时,能被完美地处理掉。
1 @SuppressLint("AppCompatCustomView")
2 public class ViewInner extends Button{
3
4 public ViewInner(Context context, AttributeSet attrs) {
5 super(context, attrs);
6 }
7
8 @Override
9 public boolean dispatchTouchEvent(MotionEvent event) {
10 Log.i("songzheweiwang","[ViewInner-->dispatchTouchEvent]event="+EventUtil.parseAction(event.getAction()));
11 return super.dispatchTouchEvent(event);
12 }
13
14 @Override
15 public boolean onTouchEvent(MotionEvent event) {
16 Log.i("songzheweiwang","[ViewInner-->onTouchEvent]event="+EventUtil.parseAction(event.getAction()));
17 return super.onTouchEvent(event);
18 }
19 }
5)辅助类:
public class EventUtil {
public static String parseAction(int action) {
String actionName = "Unknow:action=" + action;
switch (action) {
case MotionEvent.ACTION_DOWN:
actionName = "ACTION_DOWN";
break;
case MotionEvent.ACTION_MOVE:
actionName = "ACTION_MOVE";
break;
case MotionEvent.ACTION_UP:
actionName = "ACTION_UP";
break;
default:
break;
}
return actionName;
}
}
3、日志
点击上图中不同的区域,会有不同的结果。这里点击最中间的View,点击其他区域的结果及分析,我们在后面再介绍。
1 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
6 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN
7 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN
8 06-07 13:35:23.524 18298-18298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
9 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP
10 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP
11 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP
12 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_UP
13 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_UP
14 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_UP
4、结果分析
该事件包含了两action:ACTION_DOWN和ACTION_UP。前我们说过,Programmer完美处理好了事件,本次流程就到这里为止了,不再传递,Boss认为团队有能力处理这类任务,所以类似的任务也会同样会交给手下的团队,所以ACTION_UP也走了类似的流程,那么整个事件就算完成了,由Programmer完美完成。整个事件的序列图如下所示:
image.png
5、ViewInner没有能力处理的情况
上面的例子中,ViewInner是一个Button,它默认是有能力处理这次Touch事件的。但是如果这是一个默认没有能力处理该时间的控件,又会是一种怎样的情形呢?咱们把ViewInner改为继承View再看看结果(仍然点击中间的ViewInner)。
1 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
6 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN
7 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN
8 06-07 15:04:25.816 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
9 06-07 15:04:25.816 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
10 06-07 15:04:25.816 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
11 06-07 15:04:25.865 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
12 06-07 15:04:25.865 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
此时,整个事件的流程就变成了现在这样,直观地表现为如下的流程图:
image.png为什么是这种结果呢?因为ViewInner继承自View,其默认情况下,是没有处理Touch事件的能力的。所以Programmer处理不了了,就一层一层网上报告,由于ViewGroupMiddle和ViewGroupOuter都继承自RelativeLayout,默认也是没有处理Touch事件的能力的,所以最后ACTION_DOWN事件就回到了Boss这里,由Boss自己来处理。Boss发现自己首先的团队无法处理这类事件,所以后面的ACTION_UP事件就自己处理了,而没有再往下派发了。这一点,和第二节中的第(4)点的第2)小点的情况②的场景是一致的。
如果在activity_event_demo.xml中为各个控件(包括父布局)加上属性[android:clickable="true"],或者在Activity中为对应控件添加监听点击事件,那么这个控件就有了处理Touch事件的能力了,就和之前使用Button的场景一样的。读者还可以试试在ViewInnner为View时,其父布局有处理Touch事件的能力时的场景(注意,要点击ViewInner来测试),那么这就是和第二节中的第(4)点的第2)小点的情况①的场景是一致的,这里咱们不再分析日志画流程图了。
四、Touch事件主要方法说明
1、Touch的三个主要方法概述
前面一直提到Touch事件的3个主要方法:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent ev),那么这三个方法的功能究竟是什么呢?这里可以看看下面的表格的总结:
image.png其实我们可以从函数名称来大致判断其功能,dispatchTouchEvent,分发触摸事件,就是把事件传递下去,准确来说就是是否要传递到子View以及自己的onInterceptTouchEvent方法和onTouchEvent方法,也就是说,不仅管子Viiew,还管自身剩下的两个回调方法。onInterceptTouchEvent,事件拦截,它只管自身子View,而不会影响到自身后面两个方法的执行,如果拦截了,可以记忆为让自己的手下们无事可做。这两个方法容易混淆,需要重点理解和记忆。
在上述表格中还可以看到,Activity是无法回调onIntercepTouchEvent方法的,因为这个方法是ViewGroup中的方法,而Activity也不是View体系中,不是视图类,所以没有这个方法。我们可以这样记忆,Activity是Boss,不是打工行列中的一员,自己的任务就是让下面的打工者没去做事情,所有该方法对他来说,没有意义。叶子View也没有这个方法,因为自己没有子View了,也没有拦截的意义。
由于这三个方法都是boolean值,再加上默认情形下会返回super.xxx,这样,每一个方法都会有三种可选值。咱们这里先了解一下每一种取值会产生怎么样的结果。
2、事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch事件发生时,Activity的dispatchTouchEvent方法会将事件传递给最外层控件的dispatchTouchEvent方法,并由该控件进行分发下去。从根元素依次往下传递,一直到最里面的叶子View,或者中途被某个控件终止,才结束这个派发过程。其分发逻辑如下:
(1)如果 return true,事件会分发到当前控件的dispatchTouchEvent方法中处理。同时事件停止往下分发,且当前控件的onInterceptTouchEvent和onTouchEvent都不会执行。
(2)如果 return false,事件停止往下派发,且当前控件的onInterceptTouchEvent和onTouchEvent也都不会执行。同时将事件返回给上一级的onTouchEvent事件,由上一级去决定处理还是继续往上传递,自己不处理。
(3)返回默认的super.dispatchTouchEvent,事件会自动分发给当前View的onInterceptTouchEvent。
3、事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在当前控件的dispatchTouchEvent方法返回默认的方式时,其拦截逻辑如下:
1)return true,表示将事件进行拦截,并将拦截到的事件交给当前控件的onTouchEvent来处理。
2)return false,表示将事件放行,事件会被传递到子View上,并由子View的dispatchTouchEvent方法继续派发。
3)return super.onInterceptTouchEvent(ev),和返回false的逻辑一样。
4、事件响应:public boolean onTouchEvent(MotionEvent ev)
(1)该方法会被执行的情形有如下两种:
1)子View没有处理事件,将事件返回来;
2)当前控件中dispatchTouchEvent返回默认的super.dispatchTouchEvent的情况下,且该控件的onInterceptTouchEvent返回false或者默认的super.onInterceptTouchEvent时。
(2)onTouchEvent方法响应逻辑如下:
1)返回true,当前事件会被处理掉。
2)返回false,当前事件不会被处理,返回给上一级的onTouchEvent方法来处理。
3)返回super.onTouchEvent,如果自己有能力处理该事件,则会处理,此时super.onTouchEvent的值为true;否则,如果自己没有能力处理该事件,则将事件返回到上一级中的onTouchEvent方法中处理,当前super.onTouchEvent的值为false。
五、Touch的3个主要方法返回值对事件分发影响的案例分析
上一节中介绍了Touch的3个主要方法的返回值下,对事件分发的处理逻辑。本节中,咱们通过修改前面这三个方法中的返回值,来验证事件的分发流程(注意:以下情况下均点击中间的ViewInner控件)。
1、ViewGroupMiddle中dispatchTouchEvent返回true,其它均返回默认值时
image.png
1 06-07 19:15:53.220 25298-25298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 19:15:53.221 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 19:15:53.221 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 19:15:53.222 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
6 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP
7 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP
8 06-07 19:15:53.238 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP
参照第四节中的结论,ViewGroupMiddle的dispatchTouchEvent返回true,事件从Acitivty,经过ViewGroupOuter分发到ViewGroupMiddle中,在其dispatchTouchEvent方法中处理。ViewGroupMiddle的onInterceptTouchEvent和onTouchEvent均不会被调用,且事件也不会再往ViewInner中传递。既然事件是在ViewGroupMiddle的dispatchTouchEvent中被处理了,在Boss EventDemoActivity看来,自己手下的团队有能力处理这类事件,所以ACTION_UP也被派发下来,走同样的流程,直到所有事件处理完毕。
2、ViewGroupMiddle中dispatchTouchEvent返回false,其它均返回默认值时
image.png1 06-07 19:31:50.093 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
6 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
7 06-07 19:31:50.151 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
8 06-07 19:31:50.151 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
参照第四节中的结论,ViewGroupMiddle的dispatchTouchEvent返回true,事件从Acitivty,经过ViewGroupOuter分发到ViewGroupMiddle中,且在dispatchTouchEvent方法中不处理此事件。ViewGroupMiddle的onInterceptTouchEvent和onTouchEvent均不会被调用,且事件也不会再往ViewInner中传递。自己处理不了事件,传递给上一级的onTouchEvent来处理,上一级也没能力处理,最后传给了EventDemoActivity的onTouchEvent。此时,在Boss看来,自己手下团队处理不了这类事件,所以后面的事件就不再传递下去,都有自己来处理。
3、ViewGroupMiddle中onInterceptTouchEvent返回true,其它均返回默认值时
image.png 1 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
6 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
7 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
8 06-07 19:41:08.895 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
9 06-07 19:41:08.900 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
10 06-07 19:41:08.901 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
事件在ViewGroupMiddle中被拦截了,事件不再派发到ViewInner中,而是交给自己的onTouchEvent来处理。前面说过,ViewGroupMiddle继承自RelativeLayout,默认是没有能力处理Touch事件的,于是就传递到上一级的onTouchEvent中,直到EventDemoActivity中的onTouchEvent方法。此时,在Boss看来,自己手下团队处理不了这类事件,所以后面的事件就不再传递下去,都有自己来处理。
4、ViewGroupMiddle中onInterceptTouchEvent返回false,其它均返回默认值时
image.png1 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
6 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN
7 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN
8 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
9 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
10 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
11 06-07 19:48:58.162 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
12 06-07 19:48:58.162 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
这种情况下,和使用默认super.onInterceptTouchEvent时是一样的,Log中中的日志也验证了这一点。事件派发流程在第三节中详细讲解过,这里就不再赘述了。
5、ViewGroupMiddle中onTouchEvent为true,其它均返回默认值时
image.png1 06-07 19:53:51.516 26711-26711/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
6 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN
7 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN
8 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
9 06-07 19:53:51.582 26711-26711/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
10 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP
11 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP
12 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP
13 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_UP
事件依次传递到ViewInner的onTouchEvent方法中,ViewInner默认没有能力处理该事件,传递到上一级ViewGroupMiddle中的onTouchEvent来处理。返回true表示被处理了,本次事件在此中止了。在Boss看来,手下团队有能力处理这类事件,所以后面的ACTION_UP事件仍然往下分发了。这里需要注意的是,ACTION_UP在ViewGroupMiddle的dispatchTouchEvent执行后直接进入到其onTouchEvent方法中了,没有经过onInterceptTouchEvent方法走,也没有往ViewInner中分发。这个场景就好像,通过ACTION_DOWN,ViewGroupMiddle已经知道自己的手下ViewInner处理不了这类任务,所以当同类任务从上面领导发放到自己这里的时候,就不用再继续往下分发,而是直接直接就处理掉了。
6、ViewGroupMiddle中onTouchEvent为false,其它均返回默认值时
image.png
1 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
3 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
4 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
6 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN
7 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN
8 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
9 06-07 20:09:49.747 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
10 06-07 20:09:49.747 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
11 06-07 20:09:49.803 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
12 06-07 20:09:49.803 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
7, 均为默认值时
当ViewGroupMiddle中onTouchEvent返回默认的super.onTouchEvent时,我们在第三节中分析过ViewInner有能处理和没有能力处理两种情况下的事件处理逻辑,这里笔者不再赘述。现在还有一个结论需要读者验证,就是都在返回默认super.xxx情况下,可以在ViewGroupMiddle中onTouchEvent方法中打印出super.onTouchEvent的值。可以发现,如果ViewGroupMiddle中onTouchEvent方法可以处理事件,则值为true,如果没有处理Touch事件的能力,则会返回false。这一点在第四节中讲过。
六、当触摸其它区域时分析
在前面分析打印log结果的时候,笔者都着重强调了要点击正中心的ViewInner。这是因为点击不同的区域,会产生不同的逻辑处理结果。那么点击区域和事件分发结果有什么样的关系呢?下面将第三节中的例子,3个主要方法都返回默认的super.xxx方法,由外到内依次点击Boss,PM,Team Leader,Programmer四个区域。得到了如下的log信息:
1 06-07 20:27:44.390 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
2 06-07 20:27:44.391 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
3 06-07 20:27:44.405 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
4 06-07 20:27:44.405 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
5
6 06-07 20:27:48.298 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
7 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
8 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
9 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
10 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
11 06-07 20:27:48.338 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
12 06-07 20:27:48.339 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
13
14 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
15 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
16 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
17 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
18 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
19 06-07 20:27:52.682 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
20 06-07 20:27:52.682 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
21 06-07 20:27:52.682 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
22 06-07 20:27:52.749 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
23 06-07 20:27:52.749 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
24
25 06-07 20:27:57.448 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN
26 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN
27 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN
28 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
29 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN
30 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN
31 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN
32 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN
33 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN
34 06-07 20:27:57.450 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
35 06-07 20:27:57.514 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP
36 06-07 20:27:57.515 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
这四次触摸事件的日志结果用空格隔开,分析该log可以发现:当点击Boss区域时,里面的三个控件均未触发事件;当点击PM区域时,Team Leader和Programmer中的没有任何动作;点击Team Leader区域时,只有Programmer没有触发任何事件;当点击Programmer区域时,4个角色均被触发。那么这个结论就很显而易见了:当点击到View系统的某一层时,事件从外往内传递时,只到被点击的那一层为止,不会再派发到其子View中。这4个场景,是不是和我们开篇第一节中提到的4种场景很相似呢?点击到哪个区域,说明原本安排的任务本身就应该由该职位的人来完成,其手下就完全可以当成是不存在的。
结语
到目前为止,Android的事件分发和传递机制就分析完了。本文中Touch事件的3个主要方法返回值均有3种情形,所以会有多种逻辑处理组合。这里选取了中间层ViewGroupMiddle来举例,只是作为代表来分析,笔者完全可以通过其它的组合来分析更多的可能情况。如果分析中有不妥当或者不准确的地方,欢迎来拍砖。
转自【https://www.cnblogs.com/andy-songwei/p/10998855.html】
网友评论