欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

彻底弄清事件分发流程之ViewGroup源码详细分析

程序员文章站 2022-03-11 21:50:12
背景:在android开发中,经常会遇到触摸事件的分发处理,事件冲突,事件消费,如果界面比较复杂,一旦出现问题,如果对事件的分发处理机制不了解的话,这将使得我们难以处理,不知道从何处着手处理,更不知道该怎么去修改。但是如果我们对事件的分发处理机制非常熟悉,那么处理这些冲突或者由于事件消费问题引起的bug,我们就能很好的处理,并且毫不费力。举个栗子:比如你在一个scrollView里面嵌套了一个RecyclerView然后在RecyclerView里面,你又放了一个自己定义的可以伸缩并且滑动展开折叠的一个...

背景:

在android开发中,经常会遇到触摸事件的分发处理,事件冲突,事件消费,如果界面比较复杂,一旦出现问题,如果对事件的分发处理机制不了解的话,这将使得我们难以处理,不知道从何处着手处理,更不知道该怎么去修改。但是如果我们对事件的分发处理机制非常熟悉,那么处理这些冲突或者由于事件消费问题引起的bug,我们就能很好的处理,并且毫不费力。
举个栗子:比如你在一个scrollView里面嵌套了一个RecyclerView然后在RecyclerView里面,你又放了一个自己定义的可以伸缩并且滑动展开折叠的一个View,这个时候就很容易出现问题,而不能达到你所期望的效果。还有等等的一些滑动冲突的问题…

由于上面所述的背景,如果我们想要在开发过程中游刃有余,并且满足ui的各种无理要求,那么事件的分发流程就是我们必须要弄清楚的一件事。好了废话不多说下面开始事件流程分析。

在Activity.java这个类中有个dispatchTouchEven() 方法,从字面意思来看是:分发触摸事件,我们来看看源码。

/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * 翻译:这个方法用来处理触摸屏幕事件,你可以重写这个方法去拦截所有的触摸屏幕事件
     * 在这些事件被分发到window之前。确保这个方法实现能够正常的处理屏幕的触摸事件。
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

从这个方法描述我们可以得到几个信息:

  1. 这个方法时处理屏幕触摸事件的。
  2. 我们可以重写这个方法来拦截所有的屏幕触摸事件,不让这个事件再向下分发了,这个事件让我自己来处理。
  3. 重写的话,一定要确保这个方法能正常处理这个事件。

往这个方法里面看

  1. 如果这个事件是【按下】事件,那么先执行onUserInteraction()方法,进入这个方法看到,这个方法是一个空的实现,里面什么都没写,而且这个方法申明是public,这就意味着:我们可以重写这个方法,在用户刚开始【按下】的时候,就会执行到我们重写的这个方法里面,可以做一些事件预处理。
  2. 把事件传递给window,如果 windowsuperDispatchTouchEvent(ev) 方法执行结果为true 代表事件被消费掉了,那么dispatchTouchEvent(MotionEvent ev)方法返回true;如果window执行结果为false 代表传下去的事件没有被消费掉,那么activity将执行自己的onTouchEvent(ev)方法,并把结果继续往上返回给调用者。

我们来看下window是如何处理的:

public Window getWindow() {
        return mWindow;
    }

我们可以看到这个getWindow()方法,返回的是一个Window对象,进入Window类查看:

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 *
 * 翻译:这个抽象类,仅存的实现类是android.view.PhoneWindow。
 */
public abstract class Window {

我们看到了关键的一个描述:The only existing implementation of this abstract class is
android.view.PhoneWindow。意思是这个抽象类,有且只有一个实现类,就是PhoneWindow。那么我们去找PhoneWindow查看:

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

在PhoneWindow里面,我们找到了superDispatchTouchEvent(MotionEvent event)这个方法,这个方法实际上是调用了mDecor的superDispatchTouchEvent(event)方法。那我们又去找mDecor是什么:

 // This is the top-level view of the window, containing the window decor.
    // 翻译:这个是window最顶层view,包含window的装饰。
    private DecorView mDecor;

在PhoneWindow中找到mDecor的申明,实际是DecorView,是window的顶层view,如果了解activity的启动流程,DecorView并不陌生。好了继续进入DecorView这个类里面去找superDispatchTouchEvent(event)方法:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

首先我们可以看到DecorView的申明,DecorView其实他的本质是一个继承自FrameLayout的View。继续找到superDispatchTouchEvent()方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

这个方法又调用了父亲的dispatchTouchEvent(event)方法,那我们点进去看父亲的实现,点进去之后发现到了ViewGroup的dispatchTouchEvent()方法,终于找到我们今天的重点了,咋一看这个方法里面的代码非常多,但是其实逻辑很简单,让我们来分析分析,由于代码片段比较长,我把这块方法逻辑大致分成三个大块

  1. 判断是否拦截代码块。
  2. 找寻消费事件的子view。
  3. 分发事件。
 /** 这个是ViewGroup的分发事件方法,主要是向下传递事件,然后返回结果给调用者 */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }


        /** 是否处理事件的局部变量,最后返回 */
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {...}

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }

        /** 返回定义的局部变量 */
        return handled;

这是整个方法的概览,我把中间if (onFilterTouchEventForSecurity(ev)) {…}这里的代码逻辑折叠起来了,这样我们先窥整个方法的全貌。这里定义了一个是否处理事件的局部变量,根据逻辑给变量赋值,最后返回给上层调用者。最关键的逻辑就在中间被折叠起来的那里。按照我们上面分的3大块,我们把中间那部分代码依次拆开:
#第一块,是否拦截逻辑:

            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 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();
            }

            // Check for interception.
            /** 是否拦截事件的标志 */
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {

                /** 如果事件是【按下】事件,或者触摸对象 mFirstTouchTarget 不为空,进入这里 */

                /**
                 * disallowIntercept, 是否被禁止拦截的标志,通过 FLAG_DISALLOW_INTERCEPT 这个标志位判断。
                 *  */
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;


                if (!disallowIntercept) {

                    /**
                     * 如果没有禁止拦截即 disallowIntercept = false,
                     * 调用ViewGroup的onInterceptTouchEvent(ev),并把返回值给 intercepted 这个局部变量。
                     * */

                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {

                    /** 如果禁止拦截,intercepted 这个局部变量置为false */
                    intercepted = false;
                }
            } else {
                /**
                 *  要进入这个判断,必须同时满足两个条件
                 *  1、事件不是Down【按下】。
                 *  2、mFirstTouchTarget 当前触摸目标为null,即没有触摸目标。
                 *  这个触摸目标的概念:消费事件的view就是触摸目标。
                 *  */
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

有些概念我做一个解释:

  1. 一个完整的事件,是从【Down】到【Up】的过程。【Dow】表示事件的开始,【Up】表示事件结束。
  2. 触摸目标:表示如果有子view消费了【Down】事件,那么我们就把这个view保存在一个叫TouchTarget的链表数据结构中,我们姑且把它称为触摸目标。也就是说,如果没有子view消费【Down】事件,那么触摸目标对象就是null。

源码中我已附上了解释,我这里在连贯的描述一下,我们只分析重点的地方:

  1. 事件传递进来 ==》如果是【Down】事件 或者 触摸目标对象不为NULL ==》先检查自己禁止拦截事件的标志FLAG_DISALLOW_INTERCEPT,如果disallowIntercept=false,那么当前ViewGroup会去调用自身的onInterceptTouchEvent方法,询问是否要拦截这个事件,返回值保存在局部变量中;如果禁止拦截,那么局部变量恒等于false。这个局部变量intercepted 将影响后面第三块逻辑,是否分发结束事件给子view,所以等我等会儿回过来再连起来看。
  2. 事件传递进来 ==》如果既不是【Down】事件,而且 触摸目标对象 为NULL, 那么这就表示,在一个完整的事件中,没有消费这个完整事件的子view,那么intercepted 这个拦截标志将一直为true。
    第一块代码分析完毕,上面主要讲是intercepted 的赋值逻辑。

#第二块:寻找消费事件的子View

            /** 定义一个触摸目标 局部变量 */
            TouchTarget newTouchTarget = null;

            /** 是否已经分发给新的触摸目标 局部变量 */
            boolean alreadyDispatchedToNewTouchTarget = false;

            /**
             *  如果canceled = false 并且 intercepted = false
             *  即:
             *      1、不是【取消】事件
             *      2、不拦截(不拦截可以理解为:不拦截就意味着可以向下传递这个事件,拦截意味着就不向下传递事件了)
             *
             *  执行以下逻辑
             *  */
            if (!canceled && !intercepted) {

                // If the event is targeting accessibility focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                /**
                 *  这个条件里面只处理【按下】事件,单个手指或者多个手指的【按下】事件,
                 *  其他事件不走这个逻辑
                 * */
                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;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        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.

                        /**
                         *  buildTouchDispatchChildList这个方法得到一个由Z从小到大排序的子view列表
                         *  Z:可以参照Z轴,距离用户越近Z值越大。
                         *  */
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();

                        final View[] children = mChildren;

                        /** 列表从后往前遍历,即Z从大到小遍历,先从离用户最近的view找起以此类推 */
                        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;
                            }

                            /**
                             * 这两有两个判断
                             * 1、子view是否可以接收触摸事件
                             * 2、触摸点是否在子view区域上
                             * 如果任一一个条件不满足,直接continue跳过,寻找一个子view
                             * */
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            /**
                             *  执行到这一步,表示子view能接收触摸事件,并且触摸点在子view的区域上
                             *  把子view所在的触摸目标,赋值给newTouchTarget这个局部变量,
                             *  如果mFirstTouchTarget=null,那么newTouchTarget=null
                             *
                             *  这个方法表示意思是:如果这个子view之前已经被保存过了,那么直接把他给newTouchTarget赋值
                             *  */
                            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);


                            /** dispatchTransformedTouchEvent 这个方法里面根据条件,是否向下分发事件 */
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                                /** 进到这个执行逻辑里面表示: 有子view消费掉了这个事件 */

                                // 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();

                                /**
                                 * addTouchTarget() 这个方法里面,把子view封装成一个触摸目标对象,
                                 * 并且mFirstTouchTarget这个全局变量被赋值为当前这个触摸目标对象
                                 * */
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);

                                /** 这个标志表示:已经分发给新的触摸对象了 */
                                alreadyDispatchedToNewTouchTarget = true;

                                /** 这里如果找到了处理事件的子View,那么退出循环 */
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }

                        /** 遍历循环走完后,将子view列表清空 */
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

这个代码块里面主要是在找寻消费事件的子view:如果当前不是【Cancel】事件并且intercepted = false,会进入这个逻辑里面寻找消费事件的子view。但是,如果我们的ViewGroup拦截这个事件,那么就不会进到这个逻辑里面,即:不会去找消费事件的子view,那么这就会导致mFirstTouchTarget永远为NULL,触摸目标为NULL。

在这个条件里面做了几件事:

  1. 把所有的子view按照Z值大小,从小到大放入一个list中。
  2. 从后往前遍历这个list,即:先从离用户最近的view开始找,以此类推。
  3. 每个子view必须满足两个条件,才能将这个事件分发给子view。
    3.1. 子view是否可以接收触摸事件。
    3.2. 触摸点在子view的区域内。
  4. 将【Down】事件分发给满足条件的这个子view。
  5. 如果满足条件的这个子view,消费了这个事件,那么将这个子view保存到触摸目标这个链表结构内并且退出遍历,即找到一个消费事件的子view后,就不会继续找了。但是如果满足条件的子view,不消费事件,那么就继续寻找。直到完全遍历完。

第二块内容大致就是这样,主要是在【Down】事件,找寻消费事件的子view,如果没有找到子view,那么触摸目标将无法赋值,也即:mFirstTouchTarget == NULL

#第三块:分发事件

            // Dispatch to touch targets.
            /**
             * mFirstTouchTarget=null
             * 其实实际的意思是:这个事件没有一个子view来消费,所以这个对象没有地方被赋值。
             * 只有当子view消费了【按下】事件后,mFirstTouchTarget这个对象才会被赋值,否则一直都是null。
             * 那么就有一个结论:如果没有子view消费【按下】事件,那么当前ViewGroup对象会把事件交给自己处理。
             * 再将处理结果返回给上层调用者。
             * */
            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.

                /** 进入到这里表示:有子view消费了【按下】事件 */

                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;

                    /**
                     * 如果
                     * 1、局部变量表示已经分发给新的触摸目标
                     * 并且
                     * 2、局部变量newTouchTarget = mFirstTouchTarget
                     *
                     * 这两个条件要满足,那必须是刚刚上面循环子view的时候找到了消费【按下】事件的子view
                     * 在上面逻辑中,已经分发事件给子view处理了,所以不能再分发一次,否则就会分发两次
                     * 【按下】事件给子view。只有除了【按下】事件的其他事件,才可以在这里分发给子view
                     *
                     * 那么这次事件就不在分发给子view处理
                     * */
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {

                        /**
                         *  局部变量 cancelChild :表示是否给子view分发一个【cancel】事件,结束掉子view的事件处理
                         *  有这种情况:如果子view消费了【按下】事件,但是如果在【MOVE】事件的时候,当前ViewGroup
                         *  拦截了事件即:intercepted = true,那么dispatchTransformedTouchEvent()这个方法
                         *  会分发一个【Cancel】事件给子view,并且将这个子view从触摸目标链表中移除。表示这个子view
                         *  不会在接收到任何事件消息了, mFirstTouchTarget 对象重置为触摸目标链表中的第一个。
                         * */
                        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                        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;
                }
            }

第三部分分发事件:

  1. mFirstTouchTarget = null 表示没有子view消费事件,那么事件将交给ViewGroup自己来处理。
  2. 如果【Down】事件的时候,找到了消费事件的子view,即mFirstTouchTarget != null
    分发给子view。
    这里有有两个关键的判断:

第一个关键:如果是【Down】那么不会重复向子view分发事件

/**
                     * 如果
                     * 1、局部变量表示已经分发给新的触摸目标
                     * 并且
                     * 2、局部变量newTouchTarget = mFirstTouchTarget
                     *
                     * 这两个条件要满足,那必须是刚刚上面循环子view的时候找到了消费【按下】事件的子view
                     * 在上面逻辑中,已经分发事件给子view处理了,所以不能再分发一次,否则就会分发两次
                     * 【按下】事件给子view。只有除了【按下】事件的其他事件,才可以在这里分发给子view
                     *
                     * 那么这次事件就不在分发给子view处理
                     * */
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    }

我们根据第二块,知道alreadyDispatchedToNewTouchTarget这个局部变量只有在【Down】事件,并且找到子view消费的情况下,才会被赋值为true,否则都是false。如果是【Down】事件传递到这里,这两个条件都为true,所以就不会再分发一次,要不然的话,就会出现分发两个【Down】事件给子view的情况,因为在第二块寻找子view的时候已经分发过一次【Down】事件了。

第二个关键:是否主动取消子view消费

 /**
                         *  局部变量 cancelChild :表示是否给子view分发一个【cancel】事件,结束掉子view的事件处理
                         *  有这种情况:如果子view消费了【按下】事件,但是如果在【MOVE】事件的时候,当前ViewGroup
                         *  拦截了事件即:intercepted = true,那么dispatchTransformedTouchEvent()这个方法
                         *  会分发一个【Cancel】事件给子view,并且将这个子view从触摸目标链表中移除。表示这个子view
                         *  不会在接收到任何事件消息了, mFirstTouchTarget 对象重置为触摸目标链表中的第一个。
                         * */
                        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;

如果intercepted = true,即表示:拦截事件,cancelChild = true,在dispatchTransformedTouchEvent() 这个方法中,就会给子view,分发一个【Cancel】事件,子view接收到【Cancel】事件,然后ViewGroup将子view触摸目标从链表中移除,后续将不在给这个子view分发事件了。

结论:可以看出是否取消子view,关键在于 intercepted 这个局部变量。这就跟我们第一块是否拦截的逻辑那里联系起来了。如果当前ViewGroup拦截事件,那么直接给子view发一个【Cancel】事件,结束子view接收事件。

到此我们把ViewGroup里面的骨干逻辑就全部了解清楚了,至于没有细小的逻辑我们就不一个个说了,比如:怎么得到的按Z排好序的列表的,怎么判断子view是否能接收事件的,触摸点是否在子view上的,这些都是具体的逻辑了,如果要一个个放到文章里面,那就实在太多了。

综合上述:我们可以以一个故事场景来描述这个分发事件流程。我们就以一个警察局破案这么一个场景来描述一下吧。我们知道一个完整的事件:以【Down】开始,以【Up】或者【Cancel】结束。现实中我们以【报案】开始,以【结案】结束。
场景描述:
1. 有一天【市*】接到【报警】,【市*】根据这个案子的严重情况,来判断是否由【市*】自己亲自处理。如果【市*】自己觉得这是个小案子,自己不想处理,那么就找【市*】下属的【*机构】去处理这个案子。
2. 【市*】根据报案人的【事发地区】,把下属【*机构】按照离【事发地区】距离由远到近以此排序,然后从最近的开始找,如果【*机构】目前有条件可以空出来处理,那么【市*】就把这个案子交给找到的这个下属【*机构】去处理,并且在【市*】这里做好记录信息,如果下属【*机构】没有条件处理,那么就继续找直到找完下属【*机构】为止,如果一个都没法处理那么就由【市*】自己来处理。如果已经有下属【*机构】来处理了,那么之后,所有关于这个案子的所有信息全部都交给这个【*机构】处理。
3. 但是【市*】一直会跟踪这个事情的严重性来判断是否需要自己来处理,如果有一天这个【案子】变严重了,【市*】说,这个案子不用你处理了,我们自己会亲自处理,然后会给下属【*机构】通知这个案子你们不用处理了。然后后续的这个【案子】信息全权由【市*】自己处理。
4. 如果下属【*机构】明确告诉【市*】说,这个事情请你不要插手,那么【市*】说,好我们不插手,然后整个【案子】在【结案】之前都全权由下属【*机构】处理。

结尾

这就是ViewGroup里面的事件分发流程,下一篇我们一起看View的分发!

本文地址:https://blog.csdn.net/u011326269/article/details/108810184