Android事件分发机制之源码完美解析(上)

学事件分发是为了什么呢?还不是为了解决滑动冲突的。

实际上,如果仅仅是为了解决滑动冲突的,大可不必看源码,只需要掌握事件分发的外在规律即可。

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的逻辑就会被得到执行。

 

到这里告一段落,这篇文章主要分析事件分发的核心机制,让我们不仅仅停留在会用的基础上,也知其所以然了。下篇文章会像流水账一样分析所有事件分发的代码。