我们知道,View是 Android 的最基本控件,不能再细分。而ViewGroup继承于 View,可以包含多个 View。手指触碰屏幕时,触摸事件可能由 ViewGroup 拦截处理了,也可能传递给 ViewGroup 内部的 Child View 去处理。

ViewGroup 的事件分发核心就是方法 dispatchTouchEvent(MotionEvent) ,主要分为几步:

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    // 1. 如果是 Down 事件,处理初始化,重置各种状态;    // 2. 检查是否拦截了事件    // 3. 检查是否取消了事件    // 4. 根据 intercepted 和 canceled 决定是否分发事件给 Child View    // 5. 根据 mFirstTouchTarget 再次分发事件}

步骤 1

// 处理初始化的 Down 事件。if (actionMasked == MotionEvent.ACTION_DOWN) {    /*     * 当开始新的手势时,放弃所有之前的状态。     * 框架可能由于应用程序切换,ANR 或其他一些状态更改而丢失了上一个手势的抬起或取消事件。     */    cancelAndClearTouchTargets(ev);    resetTouchState();}

步骤 1 说明事件分发的起始就是 Down 事件。当接收到 Down 事件,说明开始了一次新的触摸事件分发。

步骤 2

// 拦截检查final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN        || mFirstTouchTarget != null) {    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (!disallowIntercept) {        intercepted = onInterceptTouchEvent(ev);        ev.setAction(action); // 重新恢复 action 以防发生了改变    } else {        intercepted = false;    }} else {    // 没有触摸目标并且不是初始化的 Down 事件,当前 ViewGroup 继续拦截触摸事件。    intercepted = true;}

步骤 2 决定了 ViewGroup 是否需要拦截事件,如果拦截那么 Child View 不会接收到此次事件或者接收到 Cancel 事件。

主动判断是否拦截的条件有两个:

  1. 当前事件是否为 Down 事件。如果是 Down 事件则进行第一次拦截判断;
  2. mFirstTouchTarget 是否为空。不为空说明上一次事件有 Child View 捕获,再一次进行拦截判断。

被动判断是否拦截的条件有一个:
disallowIntercept 是否为true。Child View 可以通过调用 ViewGroup 的方法requestDisallowInterceptTouchEvent(boolean)来控制 ViewGroup 是否拦截 Child View 的事件。当该变量为true,ViewGroup 不拦截 Child View 的事件。

步骤 3

// 取消检查final boolean canceled = resetCancelNextUpFlag(this)        || actionMasked == MotionEvent.ACTION_CANCEL;

步骤 3 判断当前事件是否为取消,影响后面对 Child View 事件的分发。

步骤 4

TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {        ···        if (actionMasked == MotionEvent.ACTION_DOWN            || (split && actionMasked == MotionEvent.ACTION_POINTER            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                ···                final int childrenCount = mChildrenCount;        if (newTouchTarget == null && childrenCount != 0) {                        ···                        final View[] children = mChildren;            for (int i = childrenCount - 1; i >= 0; i--) {                final int childIndex = getAndVerifyPreorderedIndex(                        childrenCount, i, customOrder);                final View child = getAndVerifyPreorderedView(                        preorderedList, children, childIndex);                                ···                                if (!child.canReceivePointerEvents()                        || !isTransformedTouchPointInView(x, y, chi                    ev.setTargetAccessibilityFocus(false);                    continue;                }                                ···                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                    ···                                        newTouchTarget = addTouchTarget(child, idBitsToAssign);                    alreadyDispatchedToNewTouchTarget = true;                    break;                }                                ···            }                        ···        }                ···    }}

步骤 4 通过canceledintercepted判断到如果不取消、不拦截当前事件,并且当前事件为 Down 事件(这里还有多指等其他情形,暂不讨论),对所有 Child View 进行遍历,找到位置处于事件范围内并且不处于动画状态的 Child View,调用dispatchTransformedTouchEvent(MotionEvent, boolean, View, int)方法将事件分发给 Child View,如果该方法返回true说明 Child View 处理了事件,在addTouchTarget(View, int)方法内部赋值mFirstTouchTarget

步骤 5

// Dispatch to touch targets.if (mFirstTouchTarget == null) {    // 没有触摸目标,把当前 ViewGroup 当做一个 View    handled = dispatchTransformedTouchEvent(ev, canceled, null,            TouchTarget.ALL_POINTER_IDS);} else {    // 分发事件给触摸目标,如果已经分发过给 newTouchTarget,则排除它。必要时则取消触摸目标。    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;            }                        ···        }                ···    }}

步骤 5 里判断到如果mFirstTouchTarget为空,说明没有 Child View 处理事件,则把 ViewGroup 当做一个普通的 View,把事件分发给自身。否则将当前以及接下来的事件分发给mFirstTouchTarget指向的 Child View 处理。handleddispatchTouchEvent(MotionEvent)的返回值,说明当前 View 处理了该事件。

更多相关文章

  1. Android四种菜单的使用
  2. Android(安卓)VelocityTracker简介
  3. Android学习笔记,Notification通知事件
  4. Android(安卓)Widget ListView添加点击事件
  5. 【Android】 onClick与onTouch并存触发的问题
  6. 【android学习】getevent和sendevent
  7. Android(安卓)studio 任意修改项目包名(含com)
  8. android RecyclerView响应点击事件
  9. Android使用ListView构造复杂界面,响应点击事件,通过Intent跳转act

随机推荐

  1. Android 防止点击事件连按,isFastClick()
  2. Android studio2.3.3升级到3.1.2坑(小记)
  3. Android Progrees处理
  4. Android 8.1 系统锁屏显示流程整理
  5. Using C++ Code in Android Application
  6. Android(安卓)6.0运行时权限解决方案
  7. ScrollView中的LinearLayout不能使用andr
  8. Android不错的图片压缩方法
  9. Android 打开关闭闪光灯工具类
  10. android 的C++代码都加 namespace androi