Android事件分发机制之源码完美解析(上)
学事件分发是为了什么呢?还不是为了解决滑动冲突的。
实际上,如果仅仅是为了解决滑动冲突的,大可不必看源码,只需要掌握事件分发的外在规律即可。
只要记住这张图,再明白内部拦截法和外部拦截法,滑动冲突这一块,都可以轻松解决了。
分享一个非常好的滑动冲突的实例:http://blog.csdn.net/qq_36523667/article/details/78825810
只需要掌握上述的内容,事件分发再无难题了。
但是,如果想掌握的更通透一点,源码是不可或缺的。因为源码里还有很多细节,enable?clickable?performClick?。。。
最重要的一个问题,如果在自定义一个view的时候,onTouchEvent返回了true,代表是消费这系列事件。而且onTouchEvent不会再调用父容器的onTouchEvent;如果返回false,会转而调用父容器的onTouchEvent。这个问题是事件分发的核心问题,可是在源码中哪里有答案???
在view group里有一段核心的代码
for (int i = childrenCount - 1; i >= 0; i--) {...
// 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); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }上面会有一个for循环,进行手指触摸区域的查找,看看是否会落在child里面。找到那个手指所落在的那个child view里。然后执行child.dispatchTouchEvent。进行一个递归查找。(为什么自动就进行一个递归查找了呢?别看这里是view哦,以为是直接就调用了view的dispatchTouchEvent。错误!比如你这个child查找到是FrameLayout,然后你其实是一个view group,所以你的dispatch touch event 肯定是view group的dispatch touch event(因为view group重写了view的dispatchTouchEvent))
所以for循环的任务是:找到当前view group中所有view、view group的区域中包含该手指落点的view、viewgroup,并进行一个递归的分发。
不过不是还有一种找不到的情况嘛!那种情况大部分是mFirstTouchTarget为空。
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }也就是上面的这段代码。而下面的else总体上就是指代的CANCEL情况,不过本文讨论的重点不是CACEL。
这种情况就是1.没有child 2.child不需要 这个时候由这个view group 调用super.dispatchTouchEvent。即把这个view group作为view,调用他自身的view.dispatchTouchEvent。
下面着重讲讲子view不为空的情况,去解决那个核心问题。
重新提一下问题:如果子view返回true,为什么就不调用父view的逻辑了?如果子view返回false,为什么就调用父view的逻辑了?
其实上面已经给出了答案。上面的源码先是一个for循环,那个for循环考虑的就是子view的情况;下面有一个if,if考虑的就是父view的情况。
如果1.view group有子view 2.且这个子view返回了true,愿意接收这个事件 这种情况mFirstTouchTarget就不为空了。所以下面的if判断条件就不成立了。而如果1.view group没有子view 2.或者这个子view 返回了 false。这个时候mFirstTouchTarget就是空了。自然就只能交给view group来处理了。
现在用最正宗的伪代码概括一下view group的代码:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { for (;;) { 递归所有的子View 1.有子View返回了true进行消费 mFirstTouchTarget = xxx 2.没有 } if (mFirstTouchTarget == null) { super.dispatchTouchEvent(ev); } else { CANCLE的情况 } }
接下来我们用源码作最后的证明:为什么子View的onTouchEvent的返回值可以决定mFirstTouchTarget?
上面的for循环的逻辑伪代码如下
TouchTarget newTouchTarget; for (;;) { if (dispatchTransformedTouchEvent(...)) { newTouchTarget = addTouchTarget(child...); } }所以我们需要查看一下,addTouchTarget方法的源码
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }所以在这里就完成了mFirstTouchTarget的赋值。(这里解释下mFirstTouchTarget的概念,他在view group中的dispatch代码中起了十分重要的作用。least recently added target。意思就是最近在for循环中搜到的并且愿意消费的view。)
解决了上述问题,继续追溯伪代码中的dispatchTransformedTouchEvent方法:
// 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); }这里是dispatchTransformedTouchEvent中的核心代码。这里是递归调用逻辑的具体实现。直接看else吧。因为for循环中传入的参数是child。(if中传入的child参数才为空)所以直接看view的dispatch代码就可以了。
view的dispatch的核心代码
if (!result && onTouchEvent(event)) { result = true; }
return result;
所以view的dispatch的返回值是由view的onTouchEvent来决定的。
所以,得出结论:如果view的onTouchEvent返回true,view的dispatch多半也会返回true,然后父view group的dispatchTransformedTouchEvent多半也会返回true,然后就mFirstTouchTarget也会被赋值,因此父view group的逻辑就不会被执行了;反之,view的onTouchEvent...然后mFirstTouchTarget依然为空,因此父view group的逻辑就会被得到执行。
到这里告一段落,这篇文章主要分析事件分发的核心机制,让我们不仅仅停留在会用的基础上,也知其所以然了。下篇文章会像流水账一样分析所有事件分发的代码。