前言
在自定义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都是管理出身,这个功能他们也不会做。于是就层层往上报,最后到老板那里,老板自己处理,是不了了之还是再找招人,或者自己也是个大牛程序员自己可以开发出来,这个决策由老板来做。老板知道自己下面的团队完成不了这一系列任务,后续一系列这类功能,就不再派发下去了。
......
上述列出了一些比较有代表性的可能情况,下面咱们根据这些情况,来理解事件的分发机制。其实这里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)辅助类:
1 public class EventUtil { 2 public static String parseAction(int action) { 3 String actionName = "Unknow:action=" + action; 4 switch (action) { 5 case MotionEvent.ACTION_DOWN: 6 actionName = "ACTION_DOWN"; 7 break; 8 case MotionEvent.ACTION_MOVE: 9 actionName = "ACTION_MOVE"; 10 break; 11 case MotionEvent.ACTION_UP: 12 actionName = "ACTION_UP"; 13 break; 14 default: 15 break; 16 } 17 return actionName; 18 } 19 }
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完美完成。整个事件的序列图如下所示:
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_DOWN11 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
此时,整个事件的流程就变成了现在这样,直观地表现为如下的流程图:
为什么是这种结果呢?因为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事件主要方法说明
前面一直提到Touch事件的3个主要方法:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent ev),那么这三个方法的功能究竟是说明呢?在哪里可以用,在哪里不能用呢?这里可以看看下面的表格:
其实我们可用从函数名称来大致判断其功能,dispatchTouchEvent,分发触摸事件,就是把事件传递下去,准确来说就是是否要传递到子View以及自己的onInterceptTouchEvent方法和onTouchEvent方法,也就是说,不仅管子Viiew,还管自身剩下的俩个回调方法。onInterceptTouchEvent,事件拦截,它只管自身子View,而不会影响到自身后面两个方法的执行,如果拦截了,可以记忆为让自己的手下们无事可做。这两个方法容易混淆,需要重点理解和记忆。
在上述表格中还可以看到,Activity是无法回调onIntercepTouchEvent方法的,因为这个方法是ViewGroup中的方法,而Activity也不是View体系中,不是视图类,所以没有这个方法。我们可以这样记忆,Activity是Boss,不是打工行列中的一员,自己的任务就是让下面的打工者没去做事情,所有该方法对他来说,没有意义。叶子View也没有这个方法,因为自己没有子View了,也没有拦截的意义。
由于这三个方法都是boolean值,再加上默认情形下会返回super.xxx,这样,每一个方法都会有三种可选值。咱们这里先了解一下没一种取值会产生怎么样的结果。
(1)事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch事件发生时,Activity的dispatchTouchEvent方法会将事件传递给最外层控件的dispatchTouchEvent方法,并由该控件进行分发下去。从根元素依次往下传递,一直到最里面的叶子View,或者中途被某个控件终止,才结束这个派发过程。其分发逻辑如下:
1)如果 return true,事件会分发到当前控件的dispatchTouchEvent方法中处理。同时事件停止往下派发,且当前控件的onInterceptTouchEvent和onTouchEvent都不会执行。(这里笔者也不清楚事件是被处理了,还是不了了之了。从打印的log上看,和被处理掉的情形很相似,本次事件,如ACTION_DOWN,到此为止,然后Activity继续给出后续事件,如ACTION_UP,继续走到当前控件的这个方法中,知道这一系列事件全部走完。但是从实验结果看,如果在acitivty中给该控件添加了点击事件,发现点击后没有响应,说明这个时间没有被处理。所以笔者很纳闷,暂时还没有找到权威的说法。)
2)如果 return false,事件停止往下派发,且当前控件的onInterceptTouchEvent和onTouchEvent也都不会执行。同时将事件返回给上一级的onTouchEvent事件,由上一级去决定处理还是继续往上传递,自己不处理。
3)返回默认的super.dispatchTouchEvent,事件会自动分发给当前View的onInterceptTouchEvent。
(2)事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在当前控件的dispatchTouchEvent方法返回默认的方式时,其拦截逻辑如下:
1)return true,表示将事件进行拦截,并将拦截到的事件交给当前控件的onTouchEvent来处理。
2)return false,表示将事件放行,事件会被传递到子View上,并由子View的dispatchTouchEvent方法继续派发。
3)return super.onInterceptTouchEvent(ev),和返回false的逻辑一样。
(3)事件响应:public boolean onTouchEvent(MotionEvent ev)
该事件会响应的情形有如下两种:1)子View没有处理事件,将事件返回来;2)当前控件中dispatchTouchEvent返回默认的super.dispatchTouchEvent的情况下,且该控件的onInterceptTouchEvent返回false或者默认的super.onInterceptTouchEvent时。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,其它均返回默认值时。
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_DOWN6 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 7 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP 8 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP 9 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,其它均返回默认值时
1 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,其它均返回默认值时
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,其它均返回默认值时
1 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,其它均返回默认值时
1 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看来,手下团队有能力处理这类事件,且在ViewGroupMiddle的onTouchEvent中完成,说明ViewInner没有能力处理,所以下一个事件ACTION_UP时,ViewGroupMiddle就没有把事件派发到ViewInner中,而是直接给自己的onTouchEvent方法中来处理。
5、ViewGroupMiddle中onTouchEvent为false,其它均返回默认值时
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
这里在activity_event_demo.xml中使用ViewGroupMiddle时添加[android:clickable="true"],将ViewGroupMiddle设置为默认可以处理Touch事件。当设置为false值时,从日志来看,表明ViewGroupMiddle中确实没有处理事件,而是传给了上级。
6, 均为默认值时
当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中。
结语
到目前为止,Android的事件分发和传递机制就分析完了。本文中Touch事件的3个主要方法返回值均有3种情形,所以会有多种逻辑处理组合。这里选取了中间层ViewGroupMiddle来举例,只是作为代表来分析,笔者完全可以通过其它的组合来分析更多的可能情况。如果分析中有不妥当或者不准确的地方,欢迎来拍砖。
原文地址:https://www.cnblogs.com/andy-songwei/p/10989172.html