Android事件分发机制(上)Touch三个重要方法的处理逻辑

  在自定义View中,经常需要处理Android事件分发的问题,尤其在有多个输入设备(如遥控、鼠标、游戏手柄等)时,事件处理问题尤为突出。Android事件分发机制,一直以来都是一个让众多开发者困扰的难点,至少笔者在工作的前几年中,没有特意研究它之前,就经常云里雾里。实际上,该问题的“七寸”就是dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)这三个方法和MotionEvent事件实体,咱们这里索性称它们为“四大恶人”吧。本文将主要通过示例演示的方式来打这个“七寸”吧。

本文的主要内容如下
image.png

一、事件分发机制与生活场景

 
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、演示界面

image.png

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.png
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,其它均返回默认值时

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.png
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,其它均返回默认值时

image.png
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看来,手下团队有能力处理这类事件,所以后面的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容