再论Android的事件分发机制

2019-01-27  本文已影响0人  勇敢地追

Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。

View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。

ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

接下来我以ViewGroup的三个方法为例

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            // 是否需要拦截.需要就调用onInterceptTouchEvent(flag标记位决定)
            // 注意FLAG_DISALLOW_INTERCEPT这个flag,可以通过requestDisallowInterceptTouchEvent来设置
            // 当标志位设置之后ViewGroup将无法拦截除了ACTION_DOWN以外的事件了
            // mFirstTouchTarget 是什么?后面的代码表示,当ViewGroup的点击事件被子View消耗,那mFirstTouchTarget就会指向该子View
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action);
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            // 是否被取消
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            if (!canceled && !intercepted) {
                // 如果没有取消也没有拦截,那就让子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.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            ......
                            会调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
                        }
                    }
            }

            // 子view未处理
            // 可能有两种情况:一、ViewGroup下面没有子View。二、子View没有消耗点击事件。
            if (mFirstTouchTarget == null) {
                // child=null,永远调用super.dispatchTouchEvent
                // handled = super.dispatchTouchEvent
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                        // alreadyDispatchedToNewTouchTarget在循环遍历的时候如果找到,会置为true
                        // handled = true表示被子view消费掉
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                            // handled = true表示被子view消费掉
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
        }

        return handled;
    }

注意其中的一个很重要的参数mFirstTouchTarget。这是个链表 (找到子view,并且消费才会添加到链表中)
再来看一下dispatchTransformedTouchEvent

    // 子ciew存在就让子view消费,不存在super.dispatchTouchEvent(event)
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // 循环遍历子view时,cancel=false.当child不存在时交给父view,否则交给子view
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ......
        return handled;
    }

总结

事件向下传递,假设被拦截(一般不会被拦截,设置了FLAG_DISALLOW_INTERCEPT表示可以拦截),此时mFirstTouchTarget == null 调用自己的onInterceptTouchEvent,处理完了return super.dispatchTouchEvent 往上抛
现在来看一般情况,没被拦截,大致情况如下图


当找到mFirstTouchTarget时.png

会一直往下找,如果找到mFirstTouchTarget,也就是上图最底下的View,那么就会在View的dispatchTouchEvent返回true表示消费。如果没有找到,说明要么没有子view,要么没有消费,都往上抛,也就是super.dispatchTouchEvent

扩展:

还记得ACTION_CANCEL么?假如点击了以后滑动到View的外面,响应了ACTION_CANCEL了呢?看dispatchTouchEvent函数,那么很明显canceled=true,也不用遍历子view,直接返回mFirstTouchTarget=null,但此时中间的ViewGroup的mFirstTouchTarget不为null啊,那么所有的滑动事件都不会再传递到最底层的View了

上一篇下一篇

猜你喜欢

热点阅读