美文网首页
再论Android的事件分发机制

再论Android的事件分发机制

作者: 勇敢地追 | 来源:发表于2019-01-27 20:03 被阅读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了

    相关文章

      网友评论

          本文标题:再论Android的事件分发机制

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