浅析安卓事件分发机制源码
2017-07-09 21:48
363 查看
最近工作需要需要做一些比较复杂的自定义View,其中事件分发的处理自然少不了,结合之前阅读过的大量资料,工作是完成了,但是对事件分发的处理总觉得很不清晰,知其然不知其所以然的感觉让人很不舒服。如果不知道事件分发原理,要是处理的情况很复杂的话,那就很难解决了。之前也看过任玉刚的《安卓开发艺术探索》对于事件分发源码的分析,但只能说大致了解了事件分发的流程,而不知其中的道理。
索性践行某位大师的名言——
“read the fucking source”
源码的阅读总是让人痛并快乐着,一是因为源码很长逻辑很复杂,二是因为源码要考虑的东西太多,所以干扰的东西实在太多,经常跟进去就迷路。
介于本文是对源码层次的分析,所以如果大家还没了解过事件分发的基本流程的话,最好先看一下这方面的资料。
首先,关于事件分发,相信接触过的人都看过类似这样一张U型图片:
并且也知道dispatchTouchEvent、InterceptTouchEvent、onTouchEvent这三个方法的作用分别分发事件、拦截事件、消费事件。
也看过类似的伪代码:
其实如果熟悉这些,简单的事件分发已经可以处理了,但是这些只是单纯记忆流程,并不知道具体的事件在源码中何去何从。
总的来说,ViewGroup和View属于树的结构,事件分发就是从父节点到子节点一步步遍历的过程,直到找到可以消费事件的View为止。而在这一过程中,就是一个不断递归调用以上伪代码的过程。子View总是在经过dispatchTouchEvent的执行后将返回值交给父View,父View根据子View是否消费了事件再确定自己是否需要消费事件,然后再向自己的父View返回一个表示自己或者自己的子View是否消费了事件的布尔值。如果当前View(ViewGroup)自己或者子View不消费就会将事件转给父View的onTouchEvent方法,所以就会呈现了上面的U型图。分析了源码之后,更能体会这其中设计的精妙
关于事件分发源码我认为最好还是模拟一个事件流,跟着代码走,努力避开各种其他代码的干扰(例如安全检查等),并且从最简单的事件入手,即单指触模、控件不进行滑动、先不考虑ACTION_CANCEL事件。
本文基于Android23的源码进行分析。请对照View和ViewGroup的源码来看本文
首先要明确一个事件序列指的是从手指按下、滑动、抬起的这一个过程中的多个事件。分别是DOWN、MOVE、UP事件。
在这里,假设一个情景,有一个Activity,里面的布局是最外层一个ViewGroup A(充满屏幕),A里面有个ViewGroup B(充满A),B里面有个Button C(比如在屏幕中间)。
情形1:
A完全(即A上的所有点都要拦截)拦截事件(即InterceptTouchEvent直接返回true),现在单指点击了一下A范围任意点,然后滑动并抬起。
(Activity、PhoneWindow 、DecorView的分发就不进行分析了)
现在的事件为ACTION_DOWN。
首先DecorView调用了ViewGroup A的dispatchTouchEvent方法(当然这里只挑关键代码),看到ViewGroup的2103行:
这一段很简单,但是很重要。关键在 intercepted = onInterceptTouchEvent(ev);,大家也很熟悉,此时A要拦截事件的,所以intercepted 为true。
于是,2134行的:
里面的语句就不会被执行(里面的语句主要是遍历子View找到消费这一系列事件的子View)
然后来到2239行:
mFirstTouchTarget就是找到的那个要消费事件的子View,此时因为根本没有遍历过子View去寻找,所以为null,所以调用:
这里dispatchTransformedTouchEvent很重要,还要注意第三个参数传了null。
进入dispatchTransformedTouchEvent方法,先看下注释:
Transforms a motion event into the coordinate space of a particular child view,filters out irrelevant pointer ids, and overrides its action if necessary.
If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
最后一句话说的很明白了,如果child(即第三个参数)为null,则该事件会传给当前的ViewGroup,即A。
对应的代码在dispatchTransformedTouchEvent方法中,处于ViewGroup的2567行:
是的,现在调用了
其实就是View的dispatchTouchEvent方法。
在View的dispatchTouchEvent中,关键看View的9285行:
可以看到如果View已经设置了onTouchListener并且返回true,则onTouchEvent并不会被执行,并且整个dispatchTouchEvent方法最后也是返回这个result。
View默认onTouchListener为null,所以onTouchEvent会被执行。
onTouchEvent中默认主要是对OnClickListener和OnLongClickListener等事件的处理。看源码View的10288行:
这里的判断语句如果条件不成立,即假如View不是Clickable(默认状态)的话,onTouchEvent则返回false,当然也不执行各种点击事件。
此时如果A是默认状态,则A的dispatchTouchEvent返回false给DecorView,它的意义是:A本身和子View都不消耗该事件。因为DecorView只有一个子View A,如果A不消费事件,那么在这一系列事件的后续事件,及MOVE、UP,就不会分发给A了。
如果A是Clickable的(比如被set了OnClickListenrer),则onTouchEvent返回true,那么返回true给DecorView,DecorView就会将后续的事件都传给它处理。而在ACTION_UP事件传递过来的时候,onTouchEvent就会触发OnClickListenrer等的点击事件。
至于为什么,且听后面分解。
在这里,可以知道,在ViewGroup拦截事件的情况下,会通过dispatchTransformedTouchEvent去调用自己的super.dispatchTouchEvent方法最后调用onTouchEvent方法,也就是把自己当做一个View处理事件。
dispatchTransformedTouchEvent很关键,具体下面会说明~~
情形2:
A不拦截事件,B拦截事件,单指点击屏幕任意点,然后滑动并抬起。
同样,首先DecorView调用了ViewGroup A的dispatchTouchEvent方法,这时候intercepted已经是false了,所以到了ViewGroup的2134行的判断
就需要进入了(此时的cancel为false,一般情况下都为false)。
此时来到ViewGroup的2144行:
目前只看第一个判断actionMasked == MotionEvent.ACTION_DOWN,其实这个判断语句内部是用于找出能够消费Down事件的子View,这个对于此次系列的事件意义重大,我们进入判断语句看。
首先看从ViewGroup的2155行开始的这一段:
很明显,ViewGroup在遍历子View,buildOrderedChildList方法这里是创建了一个从顶到底的子View集合去遍历,所以越顶部的View越优先可以消费事件。最后主要是使用isTransformedTouchPointInView方法判断触摸点是否在子View上。
B是A的子View,并且触摸点在B上,所以不会执行continue,即继续执行后面的代码。
来到ViewGroup的2199行:
又见到了isTransformedTouchPointInView方法,
进入该方法,跳过关于ACTION_CANCEL以及多指触控的代码,主要就是一下代码:
这里主要是分child是否为null两种情况,在这里先不讨论null的情况,这里的child为通过遍历且确定触摸点在其中的View。
当child不为null的时候,先做一些滑动偏移量处理,然后调用child的dispatchTouchEvent方法。
是的,这里就是真正实现事件分发的地方,开始将事件传递到子View的dispatchTouchEvent方法。在已经确定触摸点在View上的情况下,调用dispatchTransformedTouchEvent方法的作用是通过自View的dispatchTouchEvent的返回值来判断该View是否要消费这个事件,true则为消费,false则不消费。
现在子View为ViewGroup B,B是拦截事件的,所以interceptTouchEvent返回true,所有B也会像情形1中的A一样执行View的dispatchTouchEvent方法,再调用onTouchEvent方法。默认情况onTouchEvent返回false,所以不会走入前面A的
里面的代码,而是会走到ViewGroup 2239行的代码:
是的,这里的mFirstTouchTarget 是在
判断语句内才会被赋值,使得它持有消费了事件的View。
如果B的onTouchEvent返回true,即B消费事件的情况下才会被赋值,它本身为一个ViewGroup内部类TouchTarget的链表,之所以为链表主要是为了多指触摸情况,这里我们暂且认为它代表的是能够消费该事件的View就可以。
走到这里是已经遍历完所有A的子View ,如果遍历完发现没有子View消费事件(mFirstTouchTarget == null),则调用:
注意到此时child参数传null,回看dispatchTransformedTouchEvent的代码就知道会调用super.dispatchTouchEvent,源码中有关于此的注释:
前面已经说过了,如果没有子View消费,当前ViewGroup就自己调用dispatchTouchEvent尝试去消费。
如果B可以消费事件呢(B的onTouchEvent返回true)?那么在A遍历到B的时候,A的判断语句中的dispatchTransformedTouchEvent就会返回true(当此时B已经执行完onTouchEvent方法),那么就会执行前面列出来的,ViewGroup的2201行开始的代码。
重点是ViewGroup的2215行:
看下addTouchTarget方法:
其实就是在以mFirstTouchTarget为头的链表插入一个新的头节点。不考虑多指触摸情况,就是类似赋值child给mFirstTouchTarget持有的意思。
这里很关键,mFirstTouchTarget 被赋值了,保存的是A中需要消费事件的子View,然后在dispatchTouchEvent剩下的代码中,会在mFirstTouchTarget 不为null的情况下,返回true,向DecorView报告A的子View或自己有View消费事件。
mFirstTouchTarget 有什么意义呢?记得事件分发中有一条规则:
一旦一个View消费了DOWN事件,那么该系列的后续事件都由该View处理。
现在DOWN事件结束了,来了MOVE事件。
同样的A的dispatchTouchEvent方法,又来到ViewGroup 2144行的:
判断语句,这次是ACTION_MOVE事件,当然无法进入语句内部,于是乎A遍历子View找出能够消费事件的View都没有执行,直接跳到前面提到的ViewGroup的2240行的代码,然后进入2244行的else语句,其中关键是2251行代码:
alreadyDispatchedToNewTouchTarget 表示是否是新添加TouchTarget的,这个在上一个事件DOWN的时候是被置为true,但是在这次事件中由于没有添加新的TouchTarget,所以为false。
所以会走到2256行:
这里的target.child就是mFirstTouchTarget中持有的View,在这里就是ViewGroup B。所以通过dispatchTransformedTouchEvent我们知道这里将当前事件ACTION_MOVE传给了B的dispatchTouchEvent方法。
总的来说就是通过mFirstTouchTarget保存DOWN事件的消费View B,然后在后续的事件直接传给了B的dispatchTouchEvent处理。
此时如果B要消费这个MOVE事件,则handle赋值为true,则A的dispatchTouchEvent返回handle为true。如果B不要消费这个MOVE事件,那么A的dispatchTouchEvent返回false给DecorView。
情形3:
A不拦截事件,B也不拦截事件,单指点击Button,然后滑动并抬起。
其实和情形2很相似了。
首先是Down事件,DecorView调用A的dispatchTouchEvent方法,A因为onInterceptTouchEvent返回false,遍历点击到的子View找到B,调用了B的dispatchTouchEvent方法,B因为onInterceptTouchEvent返回false,遍历子View找到Button C,Button因为本身为Clickable,所以dispatchTouchEvent方法返回true给B,所以B将C记录在B的mFirstTouchTarget中,然后B的dispatchTouchEvent返回true给A,告诉它“我这边可以处理这个事件”,然后A将B记录在A的mFirstTouchTarget中,A的dispatchTouchEvent方法返回true给DecorView。
于是等到MOVE事件下来,A直接找A的mFirstTouchTarget持有的View,即B,B直接找B的mFirstTouchTarget持有的View C,如果C要消费这个事件那还是一路往上返回true。
那如果C这时候不要消费事件呢?那么C的dispatchTouchEvent返回false,B的dispatchTouchEvent返回false,且还不能调用自己的super.dispatchTouchEvent处理,所以又将false返回给A,A也和B一样,所以因为递归最终这个事件交给了Activity处理。
这就是事件分发规则中:
“如果View不消耗DOWN以外的其他事件,则父View不会调用onTouchEvent处理这个事件,同时该View仍然可以继续接收到后续的事件,这些View不处理的事件都交给Activtiy处理”。
那如果View不消耗DOWN事件呢?其实前面情形1已经简单说过了,这里结合A,B,C以及后面两个情形一起来说会更加直观。就是C如果不接受DOWN事件,那么B的onTouchEvent方法会处理事件(不考虑onTouchListener情况),如果B不可以消费DOWN事件,则调用A的onTouchEvent方法处理。所以出现了U型图那样将事件往上抛的情形。
如果B可以消费DOWN事件,那么C的
mFirstTouchTarget就记录为B,此时B的mFirstTouchTarget为null,所以后续事件来到C后,C直接交给了B,B因为事件不是ACTION_DOWN了,所以不会遍历子View,直接判断mFirstTouchTarget是否为null。由于没有遍历子View,所以mFirstTouchTarget仍然为null,所以B会调用自己的super.dispatchTouchEvent处理,以之前分析的类推,后续事件不会被C接收(这部分可以重看下ViewGroup的2144行的判断语句)。
这就对应了另一个事件分发的规律:
一旦一个View在onTouchEvent中不消耗事件,则后续的事件都不会交给他来处理。(看源码发现如果DOWN触摸点在多个View中,应该说是这几个View都不消耗事件为前提?)
最后,当一个系列事件结束之后,新的系列事件来到的时候,会将上一次的 保存的状态清空(比如mFirstTouchTarget),看ViewGroup的dispatchTouchEvent在ViewGroup中的2094行:
关于事件分发机制就先简单说到这里,还有很多东西没有提及,比如多指触摸、CANCEL事件等。事件分发由于涉及递归,有时候一层层进入又一层层出来很容易让人迷路。我也是对源码研究不深入,难免有疏漏或者错误的地方,望各位指正~~
索性践行某位大师的名言——
“read the fucking source”
源码的阅读总是让人痛并快乐着,一是因为源码很长逻辑很复杂,二是因为源码要考虑的东西太多,所以干扰的东西实在太多,经常跟进去就迷路。
介于本文是对源码层次的分析,所以如果大家还没了解过事件分发的基本流程的话,最好先看一下这方面的资料。
首先,关于事件分发,相信接触过的人都看过类似这样一张U型图片:
并且也知道dispatchTouchEvent、InterceptTouchEvent、onTouchEvent这三个方法的作用分别分发事件、拦截事件、消费事件。
也看过类似的伪代码:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean result = false; // 默认状态为没有消费过 if (!onInterceptTouchEvent(ev)) { // 如果没有拦截交给子View result = child.dispatchTouchEvent(ev); } if (!result) { // 如果事件没有被消费,询问自身onTouchEvent result = onTouchEvent(ev); } return result; }
其实如果熟悉这些,简单的事件分发已经可以处理了,但是这些只是单纯记忆流程,并不知道具体的事件在源码中何去何从。
总的来说,ViewGroup和View属于树的结构,事件分发就是从父节点到子节点一步步遍历的过程,直到找到可以消费事件的View为止。而在这一过程中,就是一个不断递归调用以上伪代码的过程。子View总是在经过dispatchTouchEvent的执行后将返回值交给父View,父View根据子View是否消费了事件再确定自己是否需要消费事件,然后再向自己的父View返回一个表示自己或者自己的子View是否消费了事件的布尔值。如果当前View(ViewGroup)自己或者子View不消费就会将事件转给父View的onTouchEvent方法,所以就会呈现了上面的U型图。分析了源码之后,更能体会这其中设计的精妙
关于事件分发源码我认为最好还是模拟一个事件流,跟着代码走,努力避开各种其他代码的干扰(例如安全检查等),并且从最简单的事件入手,即单指触模、控件不进行滑动、先不考虑ACTION_CANCEL事件。
本文基于Android23的源码进行分析。请对照View和ViewGroup的源码来看本文
首先要明确一个事件序列指的是从手指按下、滑动、抬起的这一个过程中的多个事件。分别是DOWN、MOVE、UP事件。
在这里,假设一个情景,有一个Activity,里面的布局是最外层一个ViewGroup A(充满屏幕),A里面有个ViewGroup B(充满A),B里面有个Button C(比如在屏幕中间)。
情形1:
A完全(即A上的所有点都要拦截)拦截事件(即InterceptTouchEvent直接返回true),现在单指点击了一下A范围任意点,然后滑动并抬起。
(Activity、PhoneWindow 、DecorView的分发就不进行分析了)
现在的事件为ACTION_DOWN。
首先DecorView调用了ViewGroup A的dispatchTouchEvent方法(当然这里只挑关键代码),看到ViewGroup的2103行:
// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //先判断是否允许该ViewGroup拦截事件 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 { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }
这一段很简单,但是很重要。关键在 intercepted = onInterceptTouchEvent(ev);,大家也很熟悉,此时A要拦截事件的,所以intercepted 为true。
于是,2134行的:
if (!canceled && !intercepted)
里面的语句就不会被执行(里面的语句主要是遍历子View找到消费这一系列事件的子View)
然后来到2239行:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
mFirstTouchTarget就是找到的那个要消费事件的子View,此时因为根本没有遍历过子View去寻找,所以为null,所以调用:
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
这里dispatchTransformedTouchEvent很重要,还要注意第三个参数传了null。
进入dispatchTransformedTouchEvent方法,先看下注释:
Transforms a motion event into the coordinate space of a particular child view,filters out irrelevant pointer ids, and overrides its action if necessary.
If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
最后一句话说的很明白了,如果child(即第三个参数)为null,则该事件会传给当前的ViewGroup,即A。
对应的代码在dispatchTransformedTouchEvent方法中,处于ViewGroup的2567行:
// Perform any necessary transformations and dispatch. 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); }
是的,现在调用了
handled = super.dispatchTouchEvent(transformedEvent);
其实就是View的dispatchTouchEvent方法。
在View的dispatchTouchEvent中,关键看View的9285行:
if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement 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; } }
可以看到如果View已经设置了onTouchListener并且返回true,则onTouchEvent并不会被执行,并且整个dispatchTouchEvent方法最后也是返回这个result。
View默认onTouchListener为null,所以onTouchEvent会被执行。
onTouchEvent中默认主要是对OnClickListener和OnLongClickListener等事件的处理。看源码View的10288行:
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)
这里的判断语句如果条件不成立,即假如View不是Clickable(默认状态)的话,onTouchEvent则返回false,当然也不执行各种点击事件。
此时如果A是默认状态,则A的dispatchTouchEvent返回false给DecorView,它的意义是:A本身和子View都不消耗该事件。因为DecorView只有一个子View A,如果A不消费事件,那么在这一系列事件的后续事件,及MOVE、UP,就不会分发给A了。
如果A是Clickable的(比如被set了OnClickListenrer),则onTouchEvent返回true,那么返回true给DecorView,DecorView就会将后续的事件都传给它处理。而在ACTION_UP事件传递过来的时候,onTouchEvent就会触发OnClickListenrer等的点击事件。
至于为什么,且听后面分解。
在这里,可以知道,在ViewGroup拦截事件的情况下,会通过dispatchTransformedTouchEvent去调用自己的super.dispatchTouchEvent方法最后调用onTouchEvent方法,也就是把自己当做一个View处理事件。
dispatchTransformedTouchEvent很关键,具体下面会说明~~
情形2:
A不拦截事件,B拦截事件,单指点击屏幕任意点,然后滑动并抬起。
同样,首先DecorView调用了ViewGroup A的dispatchTouchEvent方法,这时候intercepted已经是false了,所以到了ViewGroup的2134行的判断
if (!canceled && !intercepted)
就需要进入了(此时的cancel为false,一般情况下都为false)。
此时来到ViewGroup的2144行:
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
目前只看第一个判断actionMasked == MotionEvent.ACTION_DOWN,其实这个判断语句内部是用于找出能够消费Down事件的子View,这个对于此次系列的事件意义重大,我们进入判断语句看。
首先看从ViewGroup的2155行开始的这一段:
final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. //从顶到底的子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); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); //如果View不包含触摸点则继续遍历 continue; }
很明显,ViewGroup在遍历子View,buildOrderedChildList方法这里是创建了一个从顶到底的子View集合去遍历,所以越顶部的View越优先可以消费事件。最后主要是使用isTransformedTouchPointInView方法判断触摸点是否在子View上。
B是A的子View,并且触摸点在B上,所以不会执行continue,即继续执行后面的代码。
来到ViewGroup的2199行:
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); alreadyDispatchedToNewTouchTarget = true; break; }
又见到了isTransformedTouchPointInView方法,
进入该方法,跳过关于ACTION_CANCEL以及多指触控的代码,主要就是一下代码:
// Perform any necessary transformations and dispatch. 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); }
这里主要是分child是否为null两种情况,在这里先不讨论null的情况,这里的child为通过遍历且确定触摸点在其中的View。
当child不为null的时候,先做一些滑动偏移量处理,然后调用child的dispatchTouchEvent方法。
是的,这里就是真正实现事件分发的地方,开始将事件传递到子View的dispatchTouchEvent方法。在已经确定触摸点在View上的情况下,调用dispatchTransformedTouchEvent方法的作用是通过自View的dispatchTouchEvent的返回值来判断该View是否要消费这个事件,true则为消费,false则不消费。
现在子View为ViewGroup B,B是拦截事件的,所以interceptTouchEvent返回true,所有B也会像情形1中的A一样执行View的dispatchTouchEvent方法,再调用onTouchEvent方法。默认情况onTouchEvent返回false,所以不会走入前面A的
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
里面的代码,而是会走到ViewGroup 2239行的代码:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
是的,这里的mFirstTouchTarget 是在
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
判断语句内才会被赋值,使得它持有消费了事件的View。
如果B的onTouchEvent返回true,即B消费事件的情况下才会被赋值,它本身为一个ViewGroup内部类TouchTarget的链表,之所以为链表主要是为了多指触摸情况,这里我们暂且认为它代表的是能够消费该事件的View就可以。
走到这里是已经遍历完所有A的子View ,如果遍历完发现没有子View消费事件(mFirstTouchTarget == null),则调用:
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
注意到此时child参数传null,回看dispatchTransformedTouchEvent的代码就知道会调用super.dispatchTouchEvent,源码中有关于此的注释:
前面已经说过了,如果没有子View消费,当前ViewGroup就自己调用dispatchTouchEvent尝试去消费。
如果B可以消费事件呢(B的onTouchEvent返回true)?那么在A遍历到B的时候,A的判断语句中的dispatchTransformedTouchEvent就会返回true(当此时B已经执行完onTouchEvent方法),那么就会执行前面列出来的,ViewGroup的2201行开始的代码。
重点是ViewGroup的2215行:
newTouchTarget = addTouchTarget(child, idBitsToAssign);
看下addTouchTarget方法:
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */ private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
其实就是在以mFirstTouchTarget为头的链表插入一个新的头节点。不考虑多指触摸情况,就是类似赋值child给mFirstTouchTarget持有的意思。
这里很关键,mFirstTouchTarget 被赋值了,保存的是A中需要消费事件的子View,然后在dispatchTouchEvent剩下的代码中,会在mFirstTouchTarget 不为null的情况下,返回true,向DecorView报告A的子View或自己有View消费事件。
mFirstTouchTarget 有什么意义呢?记得事件分发中有一条规则:
一旦一个View消费了DOWN事件,那么该系列的后续事件都由该View处理。
现在DOWN事件结束了,来了MOVE事件。
同样的A的dispatchTouchEvent方法,又来到ViewGroup 2144行的:
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)
判断语句,这次是ACTION_MOVE事件,当然无法进入语句内部,于是乎A遍历子View找出能够消费事件的View都没有执行,直接跳到前面提到的ViewGroup的2240行的代码,然后进入2244行的else语句,其中关键是2251行代码:
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; }
alreadyDispatchedToNewTouchTarget 表示是否是新添加TouchTarget的,这个在上一个事件DOWN的时候是被置为true,但是在这次事件中由于没有添加新的TouchTarget,所以为false。
所以会走到2256行:
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; }
这里的target.child就是mFirstTouchTarget中持有的View,在这里就是ViewGroup B。所以通过dispatchTransformedTouchEvent我们知道这里将当前事件ACTION_MOVE传给了B的dispatchTouchEvent方法。
总的来说就是通过mFirstTouchTarget保存DOWN事件的消费View B,然后在后续的事件直接传给了B的dispatchTouchEvent处理。
此时如果B要消费这个MOVE事件,则handle赋值为true,则A的dispatchTouchEvent返回handle为true。如果B不要消费这个MOVE事件,那么A的dispatchTouchEvent返回false给DecorView。
情形3:
A不拦截事件,B也不拦截事件,单指点击Button,然后滑动并抬起。
其实和情形2很相似了。
首先是Down事件,DecorView调用A的dispatchTouchEvent方法,A因为onInterceptTouchEvent返回false,遍历点击到的子View找到B,调用了B的dispatchTouchEvent方法,B因为onInterceptTouchEvent返回false,遍历子View找到Button C,Button因为本身为Clickable,所以dispatchTouchEvent方法返回true给B,所以B将C记录在B的mFirstTouchTarget中,然后B的dispatchTouchEvent返回true给A,告诉它“我这边可以处理这个事件”,然后A将B记录在A的mFirstTouchTarget中,A的dispatchTouchEvent方法返回true给DecorView。
于是等到MOVE事件下来,A直接找A的mFirstTouchTarget持有的View,即B,B直接找B的mFirstTouchTarget持有的View C,如果C要消费这个事件那还是一路往上返回true。
那如果C这时候不要消费事件呢?那么C的dispatchTouchEvent返回false,B的dispatchTouchEvent返回false,且还不能调用自己的super.dispatchTouchEvent处理,所以又将false返回给A,A也和B一样,所以因为递归最终这个事件交给了Activity处理。
这就是事件分发规则中:
“如果View不消耗DOWN以外的其他事件,则父View不会调用onTouchEvent处理这个事件,同时该View仍然可以继续接收到后续的事件,这些View不处理的事件都交给Activtiy处理”。
那如果View不消耗DOWN事件呢?其实前面情形1已经简单说过了,这里结合A,B,C以及后面两个情形一起来说会更加直观。就是C如果不接受DOWN事件,那么B的onTouchEvent方法会处理事件(不考虑onTouchListener情况),如果B不可以消费DOWN事件,则调用A的onTouchEvent方法处理。所以出现了U型图那样将事件往上抛的情形。
如果B可以消费DOWN事件,那么C的
mFirstTouchTarget就记录为B,此时B的mFirstTouchTarget为null,所以后续事件来到C后,C直接交给了B,B因为事件不是ACTION_DOWN了,所以不会遍历子View,直接判断mFirstTouchTarget是否为null。由于没有遍历子View,所以mFirstTouchTarget仍然为null,所以B会调用自己的super.dispatchTouchEvent处理,以之前分析的类推,后续事件不会被C接收(这部分可以重看下ViewGroup的2144行的判断语句)。
这就对应了另一个事件分发的规律:
一旦一个View在onTouchEvent中不消耗事件,则后续的事件都不会交给他来处理。(看源码发现如果DOWN触摸点在多个View中,应该说是这几个View都不消耗事件为前提?)
最后,当一个系列事件结束之后,新的系列事件来到的时候,会将上一次的 保存的状态清空(比如mFirstTouchTarget),看ViewGroup的dispatchTouchEvent在ViewGroup中的2094行:
// Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }
关于事件分发机制就先简单说到这里,还有很多东西没有提及,比如多指触摸、CANCEL事件等。事件分发由于涉及递归,有时候一层层进入又一层层出来很容易让人迷路。我也是对源码研究不深入,难免有疏漏或者错误的地方,望各位指正~~
相关文章推荐
- 安卓中的事件分发机制源码解析
- 安卓中的事件分发机制源码解析
- 安卓中的事件分发机制源码解析
- 安卓中的事件分发机制源码解析
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 。
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)