android的事件分发
‘壹’ Android Touch事件分发处理机制详解
Android应用的开发过程不可能不涉及到Touch事件的处理,简单地如设置OnClickListener、OnLongClickListener等监听器处理View的点击事件,复杂地如在自定义View中通过重写onTouchEvent来捕获用户交互事件以定制出各种效果,在使用的过程中或多或少会遇到一些奇怪的Bug,让你对Touch事件“从哪来,到哪去”产生迷之疑惑,经过多少次徘徊之后终于决定系统的分析下源码,本文就给大家分享下我的收获。
MotionEvent作为Touch事件的载体,采用时间片来管理Touch事件所有相关行为的数据,本文这样理解时间片这个概念:
通常MotionEvent会将触发当前事件的Pointer作为主要Pointer,其PointerIndex为0,而MotionEvent通过提供getX()这类不带index参数的接口以更方便的操作主要Pointer的数据。
了解了MotionEvent的组成结构之后,接下来就可以分析MotionEvent包含的事件类型了,MotionEvent通过getAction接口来获取事件Action,而Action中低8位地址存储的是事件类型(对于触摸事件来说,主要包括Down、Move、Up、Cancel、PointerDown、PointerUp),高8位地址存储的是PointerId(当事件类型为PointerDown、PointerUp时)。通常来说事件会以Down开始,以Up或Cancel结束,各事件所承担的角色以及各自的特点在分析事件分发与处理的过程时再详细说明。
另外,MotionEvent中的Flag需要说明一下:
本文仅分析Touch事件在Framework中Java层的传递,因此从事件传递到Activity开始分析。当Touch事件传递给Activity时,会调用Activity.dispatchTouchEvent(MotionEvent),Activity会将事件传递给其Window进行处理,实际会调用PhoneWindow.superDispatchTouchEvent(MotionEvent),PhoneWindow会将该事件传递给Android中View层级前埋中的顶层View(即DecorView)进行处理:
在Window未设置Callback的情况下,会调用父类的dispatchTouchEvent,DecorView继承自FrameLayout,然后FrameLayout并未实现dispatchEvent,因此最终调用ViewGroup.dispatchTouchEvent,也就是Touch事件分发的核心逻辑所在,前文中提到MotionEvent中事件类型主要包括Down、Move、Up、Cancel、PointerDown、PointerUp,而dispatchTouchEvent根据事件的不同类型会做不同处理,因此这里分别进行分析:
Down事件处理
非异常情况下,Touch事件的事件周期总是以Down事件开始的,因此Down事件在整个事件分发逻辑中起关键作用,将决定了后续Move、Up及Cancel事件的处理主体,先看一张Down事件分发的流程图:
从流程图中可以看到,Down事件的分发逻辑主要目的在于寻找到能处理该Touch事件的View控件(该View为以当前ViewGroup为Root节点的View层级中的View,利用寻找到的View创建事件处理Target),整个处理逻辑主要包含以下渣悔陆几步:
Move、Up、Cancel事件处理
完成Down事件的分发逻辑后,就确定了该Down事件后续Move、Up及Cancel事件的处理主体(注意:这里并没有确定PointerDown事件的处理主体,关于PointerDown事件的分发逻辑稍后分析),先通过一张流程图来感受下Move、Up、Cancel事件的分发逻辑:
从流程图可以看出,对于Move、Up、Cancel事件的分发步骤如如顷下:
PointerDown事件处理
PointerDown事件是在支持多Pointer(调用将FLAG_SPLIT_MOTION_EVENTS置位)的环境下,当有新的Pointer按下时产生的,该事件处理的特殊性在于会重新遍历View层级,寻找可以处理新Pointer事件的Target,具体流程参考Down事件的分发逻辑;遍历结束若仍没有找到处理该事件的Target,则会将新Pointer的处理权设置给已有Target中最早被添加的Target。完成Target的寻找之后,会将该事件通过dispatchTransformedTouchEvent传递至所有已有Target进行处理,可以通过下面流程图,对PointerDown事件的处理有一个更全局的认识:
PointerUp事件处理
相对于Up事件来说,对于PointerUp事件的处理区别在于当传递至所有已有Target结束之后并不能标记以Down事件起始的整个事件周期结束,仅能标记其关联Pointer(以PointerDown事件起始)的事件周期结束,因此不会清除所有状态,而仅会从已有Target中移除掉与该Pointer相关的部分。
onInterceptTouchEvent
在ViewGroup进行事件分发的过程中,会调用该函数来确定是否需要拦截事件,当该函数返回true时该事件将会被拦截,即不会进行正常的View层级传递,而是直接由该ViewGroup来处理,而拦截后的操作需要根据拦截事件的类型不同而不同:
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)
在将事件传递给Target进行处理之前会调用该函数对MotionEvent进行处理:
MotionEvent.split(int idBits)
判断一个View控件是否消费一个事件,是由View.dispatchEvent的返回值来决定的,而View.dispatchEvent用于寻找事件的最终消费者,话不多说,还是通过一张流程图来个直观感受:
从流程图中可以看出,View会根据ouch事件对Scroll状态进行调整,并寻找该事件的最终处理器:
View.dispatchEvent将向其直接ViewGroup返回是否消费掉该事件,返回值将决定上级ViewGroup是否需要继续询问其他子View是否需要消费该事件。这就是View中分发事件的逻辑,真是简单粗暴!
从View.dispatchEvent的分析中可以发现当未对View设置mTouchListener或mTouchListener未消费掉该事件时,Touch事件最终将由View.onTouchEvent来决定是否消费,自定义View可以重写该方法实现自身的逻辑,此处仅分析View中的通用处理逻辑:
从上述分析可以很开心地发现熟悉的onClick及onLongClick事件的产生逻辑,若是之前没看过类似的文章,应该会有原来如此的感觉吧,哈哈~~
至此,Touch事件的分发与处理流程算是走通了,个人看完整个源码之后有种豁然开朗的感觉,能很清晰的分析向“为什么事件有时候传到某个View有时候却不传?”、“有时候只传前面几个事件后面却不传了?”等问题,也希望本文的分析能让你更清晰地感知Android中Touch事件的传递流程,如果发现文中有何错误,希望不吝赐教!
‘贰’ android事件分发机制是什么设计模式
android事件分发机制 就是一个触摸事件发生了,从一个窗口传递到一个视图,再传递到另外一个视图,最后被消费的过程,在android中还是比较复杂的传递流程如下:
(1) 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
(2) 事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
(3) 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
(4) 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
(5) OnTouchListener优先于onTouchEvent()对事件进行消费。
上面的消费即表示相应函数返回值为true。
‘叁’ 谁可以解释下,android事件分发为什么要设计成从根view到子view,而不是从子vie
Android事件传递流程在网上可以找到很多资料,FrameWork层输入事件和消费事件,可以参考:
Touch事件派发过程详解
这篇blog阐述了底层是如何处理屏幕输,并往上传递的。Touch事件传递到Activity的DecorView时,往下走就是ViewGroup和子View之间的事件传递,可以参考郭神的这两篇博客
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
郭神的两篇博客清楚明白地说明了View之间事件传递的大方向,但是具体的一些晦暗的细节阐述较少,本文主要是总结这两篇博客的同时,侧重于两点:
事件分发过程中一些细节到底如何实现的?
子view到底如何和父View抢事件,父View又是如何拦截事件不发送给子View,以及如果我们需要处理这种混乱的关系才能让两者和谐相处?。
MotionEvent抽象
要明白View的事件传递,很有必要先说一下Touch事件是如何在Android系统中抽象的,这主要使用的就是MotionEvent。这个类经历了几次重大的修改,一次是在2.x版本支持多点触摸,一次是4.x将大部分代码甩给native层处理。
一次简单的事件
我们先举个栗子来说明一次完整的事件,用户触屏 滑动 到手机离开屏幕,这认为是一次完整动作序列(movement traces)。一个动作序列中包含很多动作Action,比如在用户按下时,会封装一个MotionEvent,分发给视图树,我们可以通过motionevent.getAction拿到这个动作是ACTION_DOWN。同样,在手指抬起时,我们可以接收到Action类型是Action_UP的MotionEvent。对于滑动(MOVE)这个操作,Android为了从效率出发,会将多个MOVE动作打包到一个MotionEvent中。通过getX getY可以获取当前的坐标,如果要访问打包的缓存数据,可以通过getHistorical**()函数来获取。
加入多点触摸
对于单点的操作来看,MotionEvent显得比较简单,但是考虑引入多点触摸呢?我们定义一个接触点为(Pointer)。每一个触摸点Pointer都会有一个当次动作序列的唯一Id和Index.MotionEvent中多个手指的操作API大部分都是通过pointerindex来进行的,如:获取不同Pointer的触碰位置,getX(int pointerIndex);获取PointerId等等。大部分情况下,pointerid == pointeridex。
我们从onTouch接受到一个MotionEvent,怎么拿到多个触碰点的信息?为了解开笔者刚开始学习这部分知识时的困惑,我们首先树立起一种概念:一个MotionEvent只允许有一个Action(动作),而且这个Action会包含触发这次Action的触碰点信息,对于MOVE操作来说,一定是当前所有触碰点都在动。只有ACTION_POINTER_DOWN这类事件事件会在Action里面指定是哪一个POINTER按下。
在MotionEvent的底层实现中,是通过一个16位来存储Action和Pointer信息(PointerIndex)。低8位表示Action,理论上可以表示255种动作类型;高8位表示触发这个Action的PointerIndex,理论上Android最多可以支持255点同时触摸,但是在上层代码使用的时候,默认多点最多存在32个,不然事件在分发的时候会有问题。
ACTION_DOWN OR ACTION_POINTER_DOWN:
这两个按下操作的区别是ACTION_DOWN是一个系列动作的开始,而ACTION_POINTER_DOWN是在一个系列动作中间有另外一个触碰点触碰到屏幕。
这部分详细的描述,请参考:
android触控,先了解MotionEvent
到这里,铺垫终于结束了,我们开始直奔主题。
View的事件传递
Android的Touch事件传递到Activity顶层的DecorView(一个FrameLayout)之后,会通过ViewGroup一层层往视图树的上面传递,最终将事件传递给实际接收的View。下面给出一些重要的方法。如果你对这个流程比较熟悉的话,可以跳过这里,直接进入第二部分。
dispatchTouchEvent
事件传递到一个ViewGroup上面时,会调用dispatchTouchEvent。代码有删减
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Attention 1 :在按下时候清除一些状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//注意这个方法
resetTouchState();
}
// Attention 2:检查是否需要拦截
final boolean intercepted;
//如果刚刚按下 或者 已经有子View来处理
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 不是一个动作序列的开始 同时也没有子View来处理,直接拦截
intercepted = true;
}
//事件没有取消 同时没有被当前ViewGroup拦截,去找是否有子View接盘
if (!canceled && !intercepted) {
//如果这是一系列动作的开始 或者有一个新的Pointer按下 我们需要去找能够处理这个Pointer的子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
//上面说的触碰点32的限制就是这里导致
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//对当前ViewGroup的所有子View进行排序,在上层的放在开始
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// canViewReceivePointerEvents visible的View都可以接受事件
// isTransformedTouchPointInView 计算是否落在点击区域上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//能够处理这个Pointer的View是否已经处理之前的Pointer,那么把
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
} }
//Attention 3 : 直接发给子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
= true;
break;
}
}
}
}
}
// 前面已经找到了接收事件的子View,如果为NULL,表示没有子View来接手,当前ViewGroup需要来处理
if (mFirstTouchTarget == null) {
// ViewGroup处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
if() {
//ignore some code
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
return handled;
}
上面代码中的Attention在后面部分将会涉及,重点注意。
这里需要指出一点的是,一系列动作中的不同Pointer可以分配给不同的View去响应。ViewGroup会维护一个PointerId和处理View的列表TouchTarget,一个TouchTarget代表一个可以处理Pointer的子View,当然一个View可以处理多个Pointer,比如两根手指都在某一个子View区域。TouchTarget内部使用一个int来存储它能处理的PointerId,一个int32位,这也就是上层为啥最多只能允许同时最多32点触碰。
看一下Attention 3 处的代码,我们经常说view的dispatchTouchEvent如果返回false,那么它就不能系列动作后面的动作,这是为啥呢?因为Attention 3处如果返回false,那么它不会被记录到TouchTarget中,ViewGroup认为你没有能力处理这个事件。
这里可以看到,ViewGroup真正处理事件是在dispatchTransformedTouchEvent里面,跟进去看看:
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//没有子类处理,那么交给viewgroup处理
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
可以看到这里不管怎么样,都会调用View的dispatchTouchEvent,这是真正处理这一次点击事件的地方。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//先走View的onTouch事件,如果onTouch返回True
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
我们给View设置的onTouch事件处在一个较高的优先级,如果onTouch执行返回true,那么就不会去走view的onTouchEvent,而我们一些点击事件都是在onTouchEvent中处理的,这也是为什么onTouch中返回true,view的点击相关事件不会被处理。
‘肆’ 饿了么开源的Android跨进程事件分发框架HermesEventBus
原理
事件收发是基于EventBus,IPC通信是基于Hermes。Hermes是一个简单易用的Android IPC库。
首先选一个进程作为主进程,将其他进程作为子进程。
每次一个event被发送都会经过以下四步:
1、使用Hermes库将event传递给主进程。
2、主进程使用EventBus在主进程内部发送event。
3、主进程使用Hermes库将event传递给所有的子进程。
4、每个子进程使用EventBus在子进程内部发送event。
用法
能在app内实现多进程event收发,也可以跨app实现event收发。
单一app内的用法
如果你在单一app内进行多进程开发,那么只需要做以下三步:
Step 1
在gradle文件中加入下面的依赖:
dependencies {
compile 'xiaofei.library:hermes-eventbus:0.1.1'
}
Step 2
在Application的onCreate中加上以下语句进行初始化:
HermesEventBus.getDefault().init(this);
Step 3
每次使用EventBus的时候,用HermesEventBus代替EventBus。
HermesEventBus.getDefault().register(this);
HermesEventBus.getDefault().post(new Event());
HermesEventBus也能够在一个进程间传递event,所以如果你已经使用了HermesEventBus,那么就不要再使用EventBus了。
多个app间的用法(使用DroidPlugin的时候就是这种情况)
如果你想在多个app间收发event,那么就做如下几步:
Step 1
在每个app的gradle文件中加入依赖:
dependencies {
compile 'xiaofei.library:hermes-eventbus:0.1.1'
}
Step 2
选择一个app作为主app。你可以选择任意app作为主app,但最好选择那个存活时间最长的app。
在使用DroidPlugin的时候,你可以把宿主app作为主app。
在主app的AndroidManifest.xml中加入下面的service:
<service android:name="xiaofei.library.hermes.HermesService$HermesService0"/>
你可以加上一些属性。
Step 3
在app间收发的事件类必须有相同的包名、相同的类名和相同的方法。
务必记住在代码混淆的时候将这些类keep!!!
Step 4
在主app的application类的onCreate方法中加入:
HermesEventBus.getDefault().init(this);
在其他app的Application类的onCreate方法中加入:
HermesEventBus.getDefault().connectApp(this, packageName);
“packageName”指的是主app的包名。
Step 5
每次使用EventBus的时候,用HermesEventBus代替EventBus。
HermesEventBus.getDefault().register(this);
HermesEventBus.getDefault().post(new Event());
HermesEventBus也能够在一个进程间传递event,所以如果你已经使用了HermesEventBus,那么就不要再使用EventBus了。
‘伍’ android怎么主动分发事件
Android中,所有的操作类型事件都由如下三个部分作为基础:
按下(ACTION_DOWN)
移动(ACTION_MOVE)
抬起(ACTION_UP)
这三部分都寄生于onTouch事件中,由MontionEvent类中定义的三个常量进行区分。
Android中与Touch事件相关的方法为:
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
‘陆’ Android事件分发机制
Android中对视图的Touch事件进行分发处理。
单手指操作:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP
多手指操作:ACTION_DOWN -> ACTION_POINTER_DOWN -> ACTION_MOVE -> ACTION_POINTER_UP -> ACTION_UP.
(1) dispatchTouchEvent() :事件分发
(2) onInterceptTouchEvent() :事件拦截
(3) onTouchEvent() :事件处理
ViewGroup 的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
View 的相关事件只有两个:dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:
点击事件达到顶级 View(一般是一个 ViewGroup),会调用 ViewGroup 的 dispatchTouchEvent 方法,如果顶级 ViewGroup 拦截事件即 onInterceptTouchEvent 返回 true,则事件由 ViewGroup 处理,这时如果 ViewGroup 的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。也就是说如果都提供的话,onTouch 会屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果设置了 mOnClickListenser,则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件,则事件会传递给它所在的点击事件链上的子 View,这时子 View 的 dispatchTouchEvent 会被调用。如此循环。
‘柒’ Android View 事件分发机制
Android 事件机制包含系统启动流程、输入管理(InputManager)、系统服务和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分发等一系列的环节。
Android 系统中将输入事件定义为 InputEvent,根据输入事件的类型又分为了 KeyEvent(键盘事件) 和 MotionEvent(屏幕触摸事件)。这些事件统一由系统输入管理器 InputManager 进行分发。
在系统启动的时候,SystemServer 会启动 WindowManagerService,WMS 在启动的时候通过 InputManager 来负责监控键盘消息。
InputManager 负责从硬件接收输入事件,并将事件通过 ViewRootImpl 分发给当前激活的窗口处理,进而分发给 View。
Window 和 InputManagerService 之间通过 InputChannel 来通信,底层通过 socket 进行通信。
Android Touch 事件的基础知识:
KeyEvent 对应了键盘的输入事件;MotionEvent 就是手势事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于 MotionEvent。
InputEvent 统一由 InputManager 进行分发,负责与硬件通信并接收输入事件。
system_server 进程启动时会创建 InputManagerService 服务。
system_server 进程启动时同时会启动 WMS,WMS 在启动的时候就会通过 IMS 启动 InputManager 来监控键盘消息。
App 端与服务端建立了双向通信之后,InputManager 就能够将产生的输入事件从底层硬件分发过来,Android 提供了 InputEventReceiver 类,以接收分发这些消息:
Window 和 IMS 之间通过 InputChannel 通信。InputChannel 是一个 pipe,底层通过 socket 进行通信。在 ViewRootImpl.setView() 过程中注册 InputChannel。
Android 事件传递机制是 先分发再处理 ,先由外部的 View 接收,然后依次传递给其内层的 View,再从最内层 View 反向依次向外层传递。
三个方法的关系如下:
分发事件:
应用了树的 深度优先搜索算法 (Depth-First-Search,简称 DFS 算法),每个 ViewGroup 都持有一个 mFirstTouchTarget, 当接收到 ACTION_DOWN 时,通过递归遍历找到 View 树中真正对事件进行消费的 Child,并保存在 mFirstTouchTarget 属性中,依此类推组成一个完整的分发链。在这之后,当接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 时,则会跳过递归流程,将事件直接分发给下一级的 Child。
ViewGroup 分发事件的主要的任务是找一个 Target,并且用这个 Target 处理事件,主要逻辑如下 :
为什么倒序查找 TouchTarget?
如果按添加顺序遍历,当 View 重叠时(FrameLayout),先添加的 View 总是能消费事件,而后添加的 View 不可能获取到事件。
拦截事件:
[1] Android 事件分发机制的设计与实现
[2] Android 事件拦截机制的设计与实现