朝花夕拾Android自定义View篇之Android事件分发及传递机制

Posted andy-songwei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了朝花夕拾Android自定义View篇之Android事件分发及传递机制相关的知识,希望对你有一定的参考价值。

前言

       在自定义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_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

此时,整个事件的流程就变成了现在这样,直观地表现为如下的流程图:

 技术图片

       为什么是这种结果呢?因为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来举例,只是作为代表来分析,笔者完全可以通过其它的组合来分析更多的可能情况。如果分析中有不妥当或者不准确的地方,欢迎来拍砖。

 

以上是关于朝花夕拾Android自定义View篇之Android事件分发及传递机制的主要内容,如果未能解决你的问题,请参考以下文章

朝花夕拾Android自定义View篇之多点触控(下)实践出真知

朝花夕拾Android自定义View篇之多点触控(上)基础知识

朝花夕拾Android性能优化篇之Android虚拟机简介

朝花夕拾Android性能篇之Android进程管理机制

朝花夕拾Android性能优化篇之Apk打包

朝花夕拾性能优化篇之AIDL与Android跨进程通信