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

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

文章目录

Android 事件分发 系列文章目录

总结

一、ViewGroup | dispatchTouchEvent 完整流程分析

二、ViewGroup | dispatchTransformedTouchEvent 完整流程分析

三、View | dispatchTouchEvent 完整流程分析

四、ViewGroup 事件分发相关源码

五、View 事件分发相关源码

总结

事件分发 主要是解决 事件冲突 问题 ,


父容器事件冲突问题主要解决方案是使用 ViewGroup 拦截事件 , 屏蔽后续的子组件事件分发 ;


子组件事件冲突 , 可以自己设置 OnTouchListener , 精准控制 按下 , 移动 , 抬起 , 取消 等触摸事件 , 自己设计具体的业务逻辑 ;






一、ViewGroup | dispatchTouchEvent 完整流程分析


第一步 : 判断是否是按下操作 ;


// 第一步 : 判断是否是按下操作 
            // 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();
            }


第二步 : 判断是否需要拦截 , 用户使用 requestDisallowInterceptTouchEvent 方法进行设置是否拦截事件 ;


// 第二步 : 判断是否需要拦截 , 用户使用 requestDisallowInterceptTouchEvent 方法进行设置是否拦截事件
            // Check for interception.
            // 判定是否拦截 
            // 用于多点触控按下操作的判定 
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 判断是否需要拦截 , 可以使用 requestDisallowInterceptTouchEvent 方法进行设置
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                  // 进行事件拦截 
                  // 该 onInterceptTouchEvent 方法只返回是否进行事件拦截 , 返回一个布尔值 , 没有进行具体的事件拦截 
                  // 是否进行拦截 , 赋值给了 intercepted 局部变量 
                  // 该值决定是否进行拦截 
                    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;
            }
            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }


第三步 : 判定该动作是否是取消动作 , 手指移出组件边界范围就是取消事件 ;


// 第三步 : 判定该动作是否是取消动作 , 手指移出组件边界范围就是取消事件 ; 
            // Check for cancelation.
            // 检查是否取消操作 , 手指是否移除了组件便捷 ; 
            // 一般情况默认该值是 false ; 
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;


第四步 : 判定是否拦截事件 , 以及是否取消事件 , 如果都为否 ;


// 第四步 : 判定是否拦截事件 , 以及是否取消事件 , 如果都为否 
    //    即不拦截事件 , 该事件也不取消 , 则执行该分支 
    //    在该分支中 , 记录该触摸事件 
    // 此处判定 , 是否拦截 
    // 假定不取消 , 也不拦截 
    // canceled 和 intercepted 二者都是 false , 才不能拦截 ; 
            if (!canceled && !intercepted) {


第五步 : 判定是否是按下操作 , 如果是 , 则记录该事件 , 如果不是 , 则不执行该分支 ;


 

// 第五步 : 判定是否是按下操作 , 如果是 , 则记录该事件 , 如果不是 , 则不执行该分支
    // 判断是否是按下操作 
    // 一个完整的动作 , 只有第一次按下 , 才执行下面的逻辑 
    // 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支 
    // 直接执行该分支后面的代码 
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {


第六步 : 对子控件进行排序 ;


 

// 第六步 : 对子控件进行排序 
                        // 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);


第八步 : 判定当前事件是否可用 , 组件可见 , 不处于动画阶段 , 手指在组件范围中 ;


   

// 第八步 : 判定当前事件是否可用 , 组件可见 , 不处于动画阶段 , 手指在组件范围中
        // X 控件范围 A , 如果手指按在 B 范围 , 不会触发 X 控件的事件 
        // 判定当前的组件是否可见 , 是否处于动画过程中 
        // ① canViewReceivePointerEvents 判定组件是否可见 , 会否处于动画 
        // ② isTransformedTouchPointInView 判定手指是否在控件上面 ; 
        // 上述两种情况 , 不触发事件 
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                // 不触发事件 
                                continue;
                            }


第九步 : 正式向子组件分发触摸事件 ;


如果分发事件被消耗掉 , 返回 true , 则记录该事件 ; 记录事件调用的 addTouchTarget 方法中 , 为 mFirstTouchTarget 成员变量赋值 ;

如果分发事件没有被消耗掉 , 返回 false ;

   

// 第九步 : 正式向子组件分发触摸事件
        //    如果分发事件被消耗掉 , 返回 true , 则记录该事件
        //    记录事件调用的 addTouchTarget 方法中 , 为 mFirstTouchTarget 成员变量赋值 
        //    如果分发事件没有被消耗掉 , 返回 false 
                            // 正式开始分发触摸事件
                            // 处理以下两种情况 : 
                            // ① 情况一 : 子控件触摸事件返回 true 
                            // ② 情况二 : 子控件触摸事件返回 false 
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {


第十步 : 判定当前是否有消费记录 , 即 Down 按下事件是否执行完毕 ;


如果有事件消费记录则 mFirstTouchTarget 成员不为空 , 此时从 TouchTarget 链表中取出相应的消费 Down 事件组件 , 直接将事件分发给该组件 ;

如果没有事件消费记录 , 则 mFirstTouchTarget 成员为空 , 此时调用 dispatchTransformedTouchEvent 方法消费自己 ;

// 第十步 : 判定当前是否有消费记录 , 即 Down 按下事件是否执行完毕
    //    如果有事件消费记录则 mFirstTouchTarget 成员不为空 , 此时从 TouchTarget 链表中取出相应的消费 Down 事件组件 , 直接将事件分发给该组件
    //    如果没有事件消费记录 , 则 mFirstTouchTarget 成员为空 , 此时调用 dispatchTransformedTouchEvent 方法消费自己 ; 
    // 上面的分支是只有第一次按下时才执行的 
    // 假如当前动作时按下以后的移动/抬起动作 
    // 则跳过上面的分支 , 直接执行后面的代码逻辑 
    // 按下之后 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;
                }
            }



目录
相关文章
|
15天前
|
算法 Linux 调度
深入探索安卓系统的多任务处理机制
【10月更文挑战第21天】 本文旨在为读者提供一个关于Android系统多任务处理机制的全面解析。我们将从Android操作系统的核心架构出发,探讨其如何管理多个应用程序的同时运行,包括进程调度、内存管理和电量优化等方面。通过深入分析,本文揭示了Android在处理多任务时所面临的挑战以及它如何通过创新的解决方案来提高用户体验和设备性能。
29 1
|
21天前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
2月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
42 2
|
4月前
|
Ubuntu 开发工具 Android开发
Repo下载AOSP源码:基于ubuntu22.04 环境配置,android-12.0.0_r32
本文介绍了在基于Ubuntu 22.04的环境下配置Python 3.9、安装repo工具、下载和同步AOSP源码包以及处理repo同步错误的详细步骤。
250 0
Repo下载AOSP源码:基于ubuntu22.04 环境配置,android-12.0.0_r32
|
2月前
|
Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
57 1
|
2月前
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
Android面试高频知识点(1) 图解 Android 事件分发机制
40 1
|
2月前
|
存储 安全 数据安全/隐私保护
探索安卓与iOS的隐私保护机制####
【10月更文挑战第15天】 本文深入剖析了安卓和iOS两大操作系统在隐私保护方面的策略与技术实现,旨在揭示两者如何通过不同的技术手段来保障用户数据的安全与隐私。文章将逐一探讨各自的隐私控制功能、加密措施以及用户权限管理,为读者提供一个全面而深入的理解。 ####
66 1
|
2月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
52 2