从源码上分析Android事件分发机制

一.案例简述

在Android开发中,事件分发机制是一块Android比较重要的知识体系,也是比较难理解的,网上很多资料都是用流程图的方式介绍事件分发的流程以及注意事项或者以部分源码的方式进行介绍,这样不能很好的帮助我们从整体上了解事件分发的过程更不能深入的从代码层次掌握整个分发的具体过程,在遇到自定义view或者事件冲突的问题的时候不能准确的找到问题所在,下面我们从源码上完整的分析和讲解事件分发整个过程,从代码层次上深入理解。

二.案例分析

1.概念

首先介绍一下事件分发的基本概念便于后面分析源码时使用。

事件分发的对象:

点击事件

事件类型:

1.  MotionEvent.ACTION_DOWN      按下View(所有事件的开始)

2.  MotionEvent.ACTION_UP         擡起View(与DOWN对应)

3.  MotionEvent.ACTION_MOVE      滑动View(中间可能产生无数个MOVE事件)

4.  MotionEvent.ACTION_CANCEL     结束事件(非人为原因)

注意事项:

MotionEvent.ACTION_CANCEL这个事件是非人为触发的,比如一个事件列,DOWN事件先传递到子View,但是当MOVE事件先传递到父View时,父View拦截了该事件,这样MOVE事件无法传递到子View,这个时候父View会传递一个CANCEL事件给子View,代表这个事件列传递到子View结束。

事件列:

   从手指接触屏幕至手机离开屏幕,这个过程产生的一系列事件。

点击事件流程:

一般情况下,事件都是以DOWN事件开始,UP事件借宿,中间有无数个MOVE

事件,如下图:

                                   

                                                                             图1.点击事件流程

事件分发的本质:

将点击事件传递到某个具体的View以及处理的整个过程

事件在哪些对象之间进行传递:

Activity、ViewGroup、View

所以要想充分理解Android分发机制,本质上是要理解:

1.Activity对点击事件的分发机制

2.ViewGroup对点击事件的分发机制

3.View对点击事件的分发机制

接下来我们会先从事件分发的最开始对象Activity讲起,然后传递到ViewGroup,最后到View的过程。

2.Activity事件分发

想要了解Activity的事件传递,首先我们需要对Activity的界面架构充分了解。下图是Activity的界面架构。

                                                                     

                                                                             图2.Activity界面架构

每个Activity都包含一个Window对象,在Android中Window对象基本上有PhoneWindow来实现,PhoneWindow将一个DecorView设置成为整个应用窗口的根View。

DecorView作为窗口界面的顶层视图,它将屏幕分成两部分一个是TitleView,一个是ContentView。ContentView是一个id为Content的FrameLayout我们平时setContentView的layout布局就是设置在这里面的。下面直接看源码来分析看到如上的源码大家就知道了一个Activity包含一个window对象,这个对象是由PhoneWindow来实现的,PhoneWindow将DecorView做为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域一个是TitleView一个是ContentView,而我们平常做应用所写的布局正是展示在ContentView中的。

下面是Android的根布局有很多种类,但是基本上大致框架一样,我们就以最普通的一种screen_simple.xml来分析,我们看一下源码。因为本文不是主要讲解Android布局加载所以省略Activity加载过程直接找到加载的布局。

                                              

                                                                                图3.Activity跟布局View

介绍了Activity的根布局之后我们直接进入Activity事件分发过程,首先一个点击事件最先进入的就是Activity的dispatchTouchEvent()方法,我们首先进入dispatchTouchEvent()方法中查看源码来了解整个过程。

                                                     

                                                                       图4.Activity的dispatchTouchEvent()

分析1:这个方法点击进入后可以看见里面没有实际代码,只是一个回调,可以在Activity中重写来监听用户和程序之间是否处于交互状态。

分析2:如果返回true说明这个事件已经被ViewGroup或者View消耗掉了不会继续执行下面的方面,如果返回false则会进入分析3中,

分析3:这个时候就会调用Activity自己的onTouchEvent方法来进行处理

接下来我们先进入分析2的方法实例中进行分析,首先getWindow()获取到的是Activity的window对象,我们上面介绍到Activity的window对象就是PhoneWindow,PhoneWindow是Window的实现类,这个时候我们可以直接进入PhoneWindow中查看superDispatchTouchEvent方法进行分析。

                                                    

                                                          图5.PhoneWindow中superDispatchTouchEvent方法

可以看见这个方法又是调用mDecor的superDispatchTouchEvent()方法,这里的mDecor就是我们上面介绍Activity架构时PhoneWindow中的内部类DecorView,我们继续查看DecorView中superDispatchTouchEvent()方法。

                                                   

                                                           图6.DecorView中superDispatchTouchEvent方法

分析1: 此处可以看到调用了父类ViewGroup的dispatchTouchEvent()方法这个时候就实现了Activity将事件传递到ViewGroup。这个时候就实现了Activity到ViewGroup的事件传递。如果这里返回true就代表ViewGroup或者ViewGroup下面的子View消费了事件,事件分发结束,如果返回false代表ViewGroup以及ViewGroup下面的子View没有消耗该事件,所以这个时候就会调用图4中Activity的onTouchEvent()方法。

我们分析一下如果这里返回的是false,Activity调用onTouchEvent()方法的具体实现。

                                                     

                                                                     图7.Activity的onTouchEvent()方法

分析1:判断该点击事件是否是发生在window边界之外的触摸事件,是在抽象类Window中实现,是的话就返回true不是就返回false,一般情况下都返回false。

分析到这里Activity的事件分发过程就已经结束,我们用流程图的方式整理一下流程。

                                                          

                                                                             图8.Activity事件分发流程图

3.ViewGroup事件分发

Activity事件分发到ViewGroup后就会由ViewGroup来进行分发,下面我们来介绍一下图5分析1中调用ViewGroup中的dispatchTouchEvent()方法。

                                     

                                                             图9.ViewGroup中dispatchTouchEvent()方法

分析1:intercepted代表是否拦截事件,拦截代表不分发到子View,不拦截就会直接传递给子View。

分析2:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改

分析3:是否重写onInterceptTouchEvent()方法,默认是false,如果重写返回true代表拦截事件分发到子View

接着往下看如果不拦截事件分发到子View也就是intercepted赋值为false。

                                     

                                     

                                                                图10.ViewGroup中dispatchTouchEvent()方法

分析1:分发给子View是否成功,默认是false,后面会通过这个判断是否分发到子View成功并且子View成功消耗事件。

分析2:是否重写onInterceptTouchEvent()方法或者子View调用requestDisallowInterceptTouchEvent(),如果没有重写进入if判断

分析3:遍历子View查找点击事件是否在子View中

分析4:canViewReceivePointerEvents(child)方法判断是否View可见并且没有播放动画
isTransformedTouchPointInView表示点击事件的座标是否落在View的范围内。

分析5:事件分发到子View或者调用viewGroup父类的dispatchTouchEvent()

接着分析5我们可以看到怎么从ViewGroup分发到子View,以及如果该点击事件不在任何一个子View中而是在ViewGroup中我们怎么处理这个点击事件。下图是分析5中的dispatchTransformedTouchEvent()的方法内部实现。

                                                          

                                                                  图11.dispatchTransformedTouchEvent()方法

分析1:如果没有找到点击事件所在的子View也就是child方法为null则调用父类的dispatchTouchEvent()方法,因为ViewGroup的父类是View也就是调用父类View的dispatchTouchEvent()方法接着会执行判断是否实现onTouch()以及onTouchEvent()方法,这里是和子View的实现方式事件消耗是一样的我们再子View中进行讲解,如果返回false就会将事件传递到Activity让Activity自己来处理,如果找到了点击事件所在的子View这时就会调用子View的dispatchTouchEvent()方法将事件传递给子View也就是完成了ViewGroup到View的事件分发。

    接下来我们用一张流程图来看一下ViewGroup的实现过程,清晰的整理一下整个ViewGroup的事件分发过程。

                                                                     

                                                                                图12.ViewGroup事件分发流程图

 

4.View事件分发

ViewGroup将事件分发到子View后也就是调用子View的dispatchTouchEvent()方法,我们分析一下最底层View是怎么消费这个事件的。

                                                  

                                                                   图13.View的dispatchTouchEvent()方法

分析1:首先判断该View有没有使用setOnTouchListener监听,这里可以看见OnTouchListener是优先于onClickListener的,ENABLED_MASK代表是否可以点击,大部分View默认是可以点击的所以为true

分析2:如果mOnTouchListener.onTouch()返回true事件结束,如果返回false代表子View没有消费这个事件,事件将会向上传递给它的ViewGroup

子View的onTouchEvent()方法我们就看一下两个地方分析一下即可。

                                                        

                                                             

                                                                            图14.子View的onTouchEvent()方法

分析1:这里可以看见在获取到ACTION_UP擡起手势时会调用performClick()这个方法内部实现就是我们经常看到的onClick()的回调方法调用,也就是点击事件监听实现的调用。

分析2:可以看见switch方法最外面会返回true也就是表示只要该事件可以点击就返回true代表消费了该事件,如果不能点击就返回false将事件向上传递给父View来处理。

分析到这里我们的View事件也已经全部分析了,我们还是以一张流程图来看一下整个View事件传递处理的流程。

                                                                 

                                                                                 图15.View事件分发流程图

5.事件分发总体流程

最后总体回顾一下整个事件分发从Activity到ViewGroup到View的过程,如下图。

                                                            

                                                                                     图16.事件分发整体流程

我们看到事件分发是从Activity一层层的分发到最内层的View,只要不拦截就会调用dispatchTouchEvent()方法一层层向下分发,分发完成后处理事件也是一样,从最里层的View开始如果最内层View不处理就会向上传递,如果ViewGroup也不处理最后会传递到Activity这个时候事件相当于没有控件处理。

 

三.案例总结

通过上面的叙述,相信大家对Android的分发机制有了比较细致的理解。为了加深大家的理解,下面做个简单的总结。

ViewGroup默认不拦截任何事件。

点击事件的分发过程如下:dispatchTouchEvent—>onTouchListener的OnTouch方法—>onTouchEvent—>onClickListener的onClick方法。从而也可以看出onTouch优先于onClick执行。

子View可以通过使用getParent().requestDisallowInterceptTouchEvent(true),阻止ViewGroup对其MOVE或UP事件进行拦截。

一个点击事件产生后,传递过程是:Activity—>ViewGroup—>View。顶级View接受到事件后,就会按照上面的规则去分发事件。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章