【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;
                }
            }



目录
相关文章
|
16天前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
39 8
|
1月前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
27 7
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。
118 3
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
2月前
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。废话不多说,总结一句:事件分发机制很重要。
110 9
|
2月前
|
开发工具 Android开发
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
32 0
|
4月前
|
安全 算法 数据安全/隐私保护
探索iOS与Android的隐私保护机制
【6月更文挑战第5天】在数字时代,隐私保护已成为用户最关心的问题之一。iOS和Android作为两大主流操作系统,各自发展出了独特的隐私保护技术。本文将深入探讨这两个平台在隐私保护方面的策略、技术和挑战。
94 3
|
4月前
|
Android开发
38. 【Android教程】Handler 消息传递机制
38. 【Android教程】Handler 消息传递机制
44 2
|
4月前
|
XML Android开发 数据格式
37. 【Android教程】基于监听的事件处理机制
37. 【Android教程】基于监听的事件处理机制
64 2
|
4月前
|
Android开发
Android 自定义View 测量控件宽高、自定义viewgroup测量
Android 自定义View 测量控件宽高、自定义viewgroup测量
46 0
下一篇
无影云桌面