【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )(二)

简介: 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )(二)

二、完整的触摸事件处理机制


完整触摸事件 :


一个完整的触摸动作 , 由 1 11 次 按下触摸事件 , 若干次 移动触摸事件 , 1 11 次 抬起触摸事件 组成 ,


1 11 个触摸动作只有 1 11 次按下操作 , 并且是整个触摸动作的起始 触摸事件 ;


一个完整的动作 , 只有第一次按下 , 才执行 子组件的 排序 , 遍历 , 事件分发 等操作 ; 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支 , 直接执行该分支后面的代码 ;


这里在第 1 11 次按下时 , 创建触摸事件记录 TouchTarget ; 假如当前动作时按下以后的移动/抬起动作 , 则跳过上面的分支 , 直接执行后面的代码逻辑 , 按下之后 mFirstTouchTarget 肯定不为空 ;



TouchTarget 事件记录封装 :


TouchTarget 对象对应着一个完整的动作 , 该动作包含 1 个按下事件 , 若干 移动 事件 , 1 个抬起事件 ;


第一次按下 , 负责构建 TouchTarget 链表 , 将消费事件的 View 组件封装到 TouchTarget 中 ;


然后的移动/抬起操作 , 不再重复的创建 TouchTarget 对象了 ; 直接使用第一次按下的 TouchTarget 对象作为当前动作的标识 , 直接向该 TouchTarget 对象中的 View 组件分发事件 ;


这也是我们按下按钮时 , 即使将手指按着移出边界 , 按钮也处于按下状态 ;



相关源码 :


public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    // First touch target in the linked list of touch targets.
    private TouchTarget mFirstTouchTarget;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    // 判断是否是按下操作 
    // 一个完整的动作 , 只有第一次按下 , 才执行下面的逻辑 
    // 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支 
    // 直接执行该分支后面的代码 
                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
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;
      // 计算 ViewGroup 父容器下面有多少个子 View 组件 ; 
                    final int childrenCount = mChildrenCount;
                    // TouchTarget newTouchTarget = null; 在上面声明为空 , 此处肯定为 null ; 
                    // childrenCount 子组件个数不为 0 
                    // 如果子组件个数为 0 , 则不走下一段代码 , 如果子组件个数大于 0 , 则执行下一段代码 ; 
                    // 说明下面的代码块中处理的是 ViewGroup 中子组件的事件分发功能 ; 
                    if (newTouchTarget == null && childrenCount != 0) {
                      // 获取单个手指的 x,y 坐标 
                        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.
                        // 子组件排序 , 按照 Z 轴排列的层级 , 从上到下进行排序 , 
                        // 控件会相互重叠 , Z 轴的排列次序上 , 
                        // 顶层的组件优先获取到触摸事件 
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
      // 倒序遍历 按照 Z 轴的上下顺序 , 排列好的组件 
      // 先遍历的 Z 轴方向上 , 放在最上面的组件 , 也就是顶层组件 
                        for (int i = childrenCount - 1; i >= 0; i--) {
                          // 获取索引
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            // 获取索引对应组件 
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, 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;
                            }
        // X 控件范围 A , 如果手指按在 B 范围 , 不会触发 X 控件的事件 
        // 判定当前的组件是否可见 , 是否处于动画过程中 
        // ① canViewReceivePointerEvents 判定组件是否可见 , 会否处于动画 
        // ② isTransformedTouchPointInView 判定手指是否在控件上面 ; 
        // 上述两种情况 , 不触发事件 
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                // 不触发事件 
                                continue;
                            }
        // 截止到此处 , 可以获取子组件进行操作   
        // 提取当前的子组件 
        // 第一次执行 getTouchTarget 代码时 , 是没有 mFirstTouchTarget 的
        // 此时第一次返回 null 
                            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;
                            }
                            resetCancelNextUpFlag(child);
                            // 正式开始分发触摸事件
                            // 处理以下两种情况 : 
                            // ① 情况一 : 子控件触摸事件返回 true 
                            // ② 情况二 : 子控件触摸事件返回 false 
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                // 如果返回值为 true , 说明该事件已经被消费了 
                                // 此时记录这个已经被消费的事件 
                                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;
                            }
                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
    // 上面的分支是只有第一次按下时才执行的 
    // 假如当前动作时按下以后的移动/抬起动作 
    // 则跳过上面的分支 , 直接执行后面的代码逻辑 
    // 按下之后 mFirstTouchTarget 肯定不为空 
    // 如果事件被消费 , 事件分发方法 dispatchTransformedTouchEvent 返回 true 
    // 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
    // 反之
    // 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false 
    // 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 
    // 
    // 还有一个逻辑就是 , 如果该事件被父容器拦截 , mFirstTouchTarget 也是 null 值
    // 调用 dispatchTransformedTouchEvent , 但是传入的子组件时 null 
    // 在 dispatchTransformedTouchEvent 方法中触发调用 if (child == null) 分支的 
    // handled = super.dispatchTouchEvent(event) 方法 , 调用父类的事件分发方法 
            // 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 {
    // TouchTarget 对象对应着一个完整的动作 , 该动作包含 1 个按下事件 , 若干 移动 事件 , 1 个抬起事件 ; 
              // 第一次按下 , 负责构建 TouchTarget 链表 , 将消费事件的 View 组件封装到 TouchTarget  中
              // 然后的移动/抬起操作 , 不再重复的创建 TouchTarget 对象了 
              // 直接使用第一次按下的 TouchTarget 对象作为当前动作的标识 
              // 直接向该 TouchTarget 对象中的 View 组件分发事件 
              // 这也是我们按下按钮时 , 即使将手指按着移出边界 , 按钮也处于按下状态 ; 
              // 事件被消费的分支 , 事件消费成功 , 会走这个分支 
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
    // 将当前所有的消费的事件以及消费的 View 组件做成了一个链表 
                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;
      // 找到了 View , 开始分发触摸事件 
                        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;
                }
            }
  }
}


目录
相关文章
|
6月前
|
Android开发 容器
Android UI设计: 什么是View和ViewGroup?
Android UI设计: 什么是View和ViewGroup?
138 0
|
12天前
|
Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
15天前
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
Android面试高频知识点(1) 图解 Android 事件分发机制
34 1
|
20天前
|
XML 前端开发 Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
28天前
|
Android开发
Android 事件分发机制详细解读
Android 事件分发机制详细解读
36 4
|
2月前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
44 7
|
3月前
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
3月前
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。废话不多说,总结一句:事件分发机制很重要。
171 9
|
3月前
|
开发工具 Android开发
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
39 0
|
5月前
|
Android开发
39. 【Android教程】触摸事件分发
39. 【Android教程】触摸事件分发
42 2