美文网首页
Android ViewGroup事件分发机制

Android ViewGroup事件分发机制

作者: htkeepmoving | 来源:发表于2018-11-07 23:30 被阅读0次
    参考资料

    鸿洋版ViewGroup事件分发机制
    郭霖版ViewGroup事件分发机制
    Android开发艺术探索

    上一篇已经分析了Android View的事件分发机制,本篇将根据源码讲解ViewGroup的事件分发机制,View的一大难题是滑动冲突,滑动冲突的理论基础就是事件分发机制,所以了解事件分发机制,也有益于大家了解冲突产生的原因,以及对冲突进行处理。
    关于事件传递机制,这里先给出一些结论,根据这些结论可以更好的理解整个传递机制:
    (1)同一事件序列是指从手指触摸屏幕的那一刻起,最手指离开屏幕的那一刻结束,在整个过程中
    所产生的一系列事件,这个事件序列以down事件开始,中间含有不定数量的move,最终以up事件结束;
    (2)事件传递都是由外向内传递的,即事件总是先传递给父view,然后再由父View传递给子view;
    通过requestDisallowInterceptTouchEvent可以在子元素中干预父View的时间分发过程,但是ACTION_DOWN除外;
    (3)如果某个View一旦开始拦截某个事件,那么同一事件序列都只能由他来处理,
    并且它的onInterceptTouchEvent不会再次被调用。
    (4)View没有onInterceptTouchEvent方法,一旦有事件传递给它,那么它的onTouchEvent就会被调用,
    View的ontouchEvent默认都会消耗事件,viewGroup默认不拦截事件,Android源码中可以看到onInterceptTouchEvent
    默认返回值为false;
    (5)子元素是否可以接受点击事件?第一,子元素是否在做动画;第二,点击事件的坐标是否落在子元素区域中。
    (6)如果所有子元素都没有处理事件,这里包含两种情况,第一ViewGroup没有子元素,第二子元素处理了点击事件,但是在dispatchTouchEvent中返回false,一般是因为子元素在onTouchEvent返回false,这两种情况ViewGroup会自己处理点击事件
    (7)事件大体传递流程:VeiwGroup的dispatchTouchEvent -> VeiwGroup的onInterceptTouchEvent ->View的dispatchTouchEvent ->View的onTouchEvent

    源码解析

    ViewGroup.dispatchTouchEvent():

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // Handle an initial down.
                // 处理原始的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.
                    // 需要在新事件开始时处理完上一个事件,并且调用resetTouchState重置状态
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    
                // Check for interception.
                // 检查当前View是否拦截事件
                // ViewGroup在如下两种情况下会判断是否拦截当前事件:事件类型为down或者mFirstTouchTarget != null。
                // 当ViewGroup不拦截事件并将事件交由子元素处理时,mFirstTouchTarget会被赋值也就是mFirstTouchTarget != null。
                // 这样当move事件和up事件到来时,并且事件已经被分发下去,那么onInterceptTouchEvent这个方法将不会再被调用。
                //所以当前ViewGroup拦截事件之后就不会再次调用onInterceptTouchEvent方法;
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
                        //调用onInterceptTouchEvent方法判断是否拦截当前事件,ViewGroup默认返回false;
                        //如果拦截事件将intercepted置为true;
                        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;
                }
    
                // 如果事件未被取消且未被拦截,如果拦截事件会将intercepted置为true;
                if (!canceled && !intercepted) {
                    //ACTION_DOWN事件 
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        // 当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理
                        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.
                            // 从上至下去寻找一个可以接收该事件的子View
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            // 遍历ViewGroup的所有子元素,然后判断子元素是否能够收到点击事件
                            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;
                                }
    
                                // 子元素是否能够接收PointerEvent,或事件有没有落在子元素的边界范围
                                if (!canViewReceivePointerEvents(child)
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                                    ev.setTargetAccessibilityFocus(false);
                                    continue;
                                }
                                resetCancelNextUpFlag(child);
                                // 如果某个子元素满足条件,那么事件就会传递给它处理,
                                // dispatchTransformedTouchEvent这个方法实际上就是调用子元素的dispatchTouchEvent方法,
                                // 如果子元素仍然是一个ViewGroup,则递归调用重复此过程。
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // Child wants to receive touch within its bounds.
                                    // 子View在其边界范围内接收事件
                                    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();
                                    // 如果子元素的dispatchTouchEvent返回true,表示子元素已经处理完事件,
                                    // 那么mFirstTouchTarget就会被赋值同时跳出for循环。
                                    // mFirstTouchTarget的赋值在addTouchTarget内部完成
                                    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();
                        }
                    }
                }
    
                // Dispatch to touch targets.
                // 如果遍历完所有的子元素事件没有被合适处理,有两种情况:
                // 1. ViewGroup没有子元素
                // 2. 子元素处理了点击事件,但是dispatchTouchEvent返回false
                // 这时ViewGroup会自己处理点击事件。
                if (mFirstTouchTarget == null) {
                    // No touch targets so treat this as an ordinary view.
                    // 这里的第三个参数child为null,此时会调用handled = super.dispatchTouchEvent(event);
                    //最终会调用ViewGroup自身的onTouchEvent来处理事件;
                    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;
                    //将处理该事件的子view复制给target,由子元素来处理该事件
                    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;
                            //该方法最终会调用子元素的dispatchTouchEvent,传给给子元素来处理事件    
                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;
                            }
                        }
                    }
                }
    
                // Update list of touch targets for pointer up or cancel, if needed.
                if (canceled
                        || actionMasked == MotionEvent.ACTION_UP
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    //UP事件到来,重置状态,例如将处理该事件的子view mFirstTouchTarget置为null;   
                    resetTouchState();
                }
            }
            return handled;
        }
    

    说明:
    (1)如果onInterceptTouchEvent返回true,表示ViewGroup将拦截事件,会将intercepted设置true,这样就不会遍历子view寻找事件接受者;这样mFirstTouchTarget为null,会调用dispatchTransformedTouchEvent(ev, canceled, null,
    TouchTarget.ALL_POINTER_IDS);

        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            if (child == null) {
                //因为传入的参数为null,所以调用ViewGroup自身的dispatchTouchEvent方法来处理事件;
                handled = super.dispatchTouchEvent(transformedEvent);
            } else {
                //当有子元素处理事件时,会调用子元素的dispatchTouchEvent
                handled = child.dispatchTouchEvent(transformedEvent);
            }
            return handled;
        }
    

    (2)当子元素处理事件时会调用addTouchTarget();

        private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
            final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
            target.next = mFirstTouchTarget;
            // 由子元素处理事件时,将子元素赋值给mFirstTouchTarget
            //这样通过mFirstTouchTarget是否为null,就可以知道事件时由子view还是ViewGroup来处理事件;
            mFirstTouchTarget = target;
            return target;
        }
    

    (3)如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发;
    (4)可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法
    (5)子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;
    (6)父View可以通过onInterceptTouchEvent来拦截事件,但是如果父view不拦截down事件,子view如果调用requestDisallowInterceptTouchEvent方法,那么即使父View在move和up的时候return true,也不会将事件拦截掉,也只会调用子view的onTouchEvent;当面对滑动冲突时,我们可以考虑通过requestDisallowInterceptTouchEvent设置FLAG_DISALLOW_INTERCEPT标志位来解决滑动冲突;如果父View拦截Down事件,那么子View将不会收到事件;
    (7)滑动冲突的理论基础是事件分发机制,所以熟悉事件分发机制有助于我们解决滑动冲突相关问题;
    以上就是事件分发机制的全部内容。最后,本文部分内容直接从其他地方文章直接Copy而来,感谢本文内容所参考文章的作者;

    相关文章

      网友评论

          本文标题:Android ViewGroup事件分发机制

          本文链接:https://www.haomeiwen.com/subject/geqjxqtx.html