当前位置:主页 > android教程 > Android  ViewGroup

Android事件分发机制 ViewGroup分析

发布:2023-03-07 11:00:01 59


给大家整理了相关的编程文章,网友从俊人根据主题投稿了本篇教程内容,涉及到Android事件分发机制、Android、、ViewGroup、Android  ViewGroup相关内容,已被431网友关注,涉猎到的知识点内容可以在下方电子书获得。

Android  ViewGroup

前言:

事件分发从手指触摸屏幕开始,即产生了触摸信息,被底层系统捕获后会传递给Android的输入系统服务IMS,通过Binder把消息发送到activity,activity会通过phoneWindow、DecorView最终发送给ViewGroup。这里就直接分析ViewGroup的事件分发

整体流程

配合图在看一段伪代码:

public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{
    val result = false  //处理结果,默认是没消费过的

    if (!onInterceptTouchEvent(ev)){ //是否拦截
        result = child.dispatchTouchEvent(ev) // 分发给子view处理
    }

    if (!result){ //事件没有消费
        if (onTouchListener != null) { //先询问是否设置了onTouchListener
            result = onTouchListener.onTouch(ev)
        }
        if (!result) { //还是没有消费就交给onTouchEvent处理
            result = onTouchEvent(ev)
        }
    }

    return result
}

这张图和这段伪代码实际上已经概括了ViewGroup和View对事件处理的整个流程,注意只有ViewGroup有拦截机制即onInterceptTouchEvent

源码分析

在分析源码之前先了解个基本概念 同一事件序列:同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束

public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        /**
         * step1
         * ACTION_DOWN是一个系列事件的起点,终点是ACTION_UP
         * 如果是ACTION_DOWN会重置一些flag并且会把mFirstTouchTarget置空
         */
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        final boolean intercepted;//变量判断消息是否被拦截
        /**
         * step2
         * 从以下代码可以看出如果事件不是ACTION_DOWN并且mFirstTouchTarget为空的话那么ViewGroup是不能再拦截同一系列的事件了
         * mFirstTouchTarget 代表的就是一个单链表,它会把处理当前这一系列事件的view保存下来
         * 假如当前事件是ACTION_MOVE,并拦截了该事件那么会在step9中把mFirstTouchTarget置空
         *
         * 结论1:
         * 如果View决定拦截一个事件那么该View的 onInterceptTouchEvent 方法不会再被调用了,
         * 同一序列事件后续的所有事件都只能由该View处理(当然前提是事件能分发到该view,有可能在上层被拦截了)
         */
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            /**
             * disallowIntercept表示是否禁用拦截功能,子view通过 requestDisallowInterceptTouchEvent 方法
             * 可以要求父view不准拦截事件,不过该方法在MotionEvent.ACTION_DOWN事件中不起作用,因为在step1中会把所有标志位重置
             *
             */
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            /**
             * 如果进不到上面的if判断则表示当前系列事件viewGroup已经拦截过某个事件了
             * intercepted 直接置为true
             */
            intercepted = true;
        }


        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        /**
         * step3
         * 看这里如果ViewGroup拦截了该事件则不会进入step3里面了,而是直接走到step9中
         */
        if (!canceled && !intercepted) {
            /**
             * step4
             * 这里我们只考虑单指的点击、移动和抬起
             * ACTION_POINTER_DOWN和多点触控有关,ACTION_HOVER_MOVE和鼠标有关
             * 所以如果当前事件是MOVE也不会走step4也是直接走到step9中找到对应的子view继而分发事件
             * 结论2:如果DOWN事件被某个view消耗那么后续的事件都会直接交给这个view(前提是父view没有拦截)
             */
            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;

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    /**
                     * step5
                     * 遍历所有的子view
                     */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(     childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(     preorderedList, children, childIndex);

                        //省略部分代码。。。
                        /**
                         * step6
                         * 当找到一个合适的子view时,在 dispatchTransformedTouchEvent 中会调用子view的dispatchTouchEvent
                         * 如果该子view消耗了事件,会把子view保存到mFirstTouchTarget对应的链表中,并结束for循环
                         *
                         * 结论3:
                         * 如果一个view没有消耗DOWN事件那么后续的事件都不会再分发给该view
                         * 该结论和结论2呼应上了,因为在这个for循环中只有子view的 dispatchTransformedTouchEvent返回true才会被加入到链表中
                         * 下一次的事件并不会再到step4中来了
                         */
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. 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(); /**  * step7  * 把子view保存到链表中,mFirstTouchTarget指向表头  * alreadyDispatchedToNewTouchTarget置为true  * 结束for循环  */ 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);
                    }
                }
            }
        }

        /**
         * step8
         * 如果拦截了事件会把 mFirstTouchTarget 置空这个时候就直接调用viewGroup的super.dispatchTouchEvent
         * 即view中的dispatchTouchEvent
         */
        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;
            /**
             * step9
             * 如果拦截了就把mFirstTouchTarget置空,没有拦截就找到对应的childView把事件分发下去
             */
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                    //注意这里cancelChild如果为true,并且target.child不为空的话,dispatchTransformedTouchEvent会把事件转成CANCEL分发给target.child
                    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;
            }
        }

    }

    return handled;
}

看下cancel事件的由来,这里需要结合上文代码step9看

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,                   View child, int desiredPointerIdBits) {
    final boolean handled;
    
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        /**
         * 把事件转换成ACTION_CANCEL
         */
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            /**
             * 如果child不为空就分发给它
             */
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    
    return handled;
}

到此这篇关于Android事件分发机制 ViewGroup分析的文章就介绍到这了,更多相关Android  ViewGroup内容请搜索码农之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持码农之家!


参考资料

相关文章

  • Android传感器的简单使用方法

    发布:2023-03-12

    这篇文章主要为大家详细介绍了Android传感器的简单使用方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下


  • Android小工具自定义view课表

    发布:2023-03-09

    这篇文章主要为大家详细介绍了Android小工具自定义view课表,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下


  • Android性能优化之plt hook与native线程监控详解

    发布:2023-03-10

    这篇文章主要为大家介绍了Android性能优化之plt hook与native线程监控详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪


  • Android Activity Results API代替onActivityResult处理页面数据

    发布:2023-03-06

    说到onActivityResult,我们已经非常熟悉来,通过在A activity启动B activity并且传入数据到B中,然后在A中通过onActivityResult来接收B中返回的数据。在最新的activity-ktx的beta版本中,谷歌已经废弃了onActivityResult


  • Android webview加载H5方法详细介绍

    发布:2023-03-02

    这篇文章主要介绍了Android webview加载H5的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


  • Android串口通讯SerialPort的使用详情

    发布:2023-03-06

    这篇文章主要介绍了Android串口通讯SerialPort的使用详情,文章围绕主题展开详细的内容戒杀,具有一定的参考价值,需要的朋友可以参考一下


  • Android View转换为Bitmap实现应用内截屏功能

    发布:2023-03-07

    这篇文章主要介绍了Android View转换为Bitmap实现应用内截屏功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


  • Android 下的 QuickJS Binding 库特性使用详解

    发布:2023-03-07

    这篇文章主要介绍了Android 下的 QuickJS Binding 库特性使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪


网友讨论