美文网首页
ViewGroup 简析dispatchTouchEvent返回

ViewGroup 简析dispatchTouchEvent返回

作者: 撸吗撸码 | 来源:发表于2022-12-26 14:08 被阅读0次

    金九银十的换工作季又到了。希望那些每天都进步的攻城狮们都能拥有一个美丽而又多金的好工作。
    事件分发机制一直是面试官喜欢问的题目,所以我们有必要彻底搞清楚。搞清楚了它不仅对我们现在的工作有益,对我们换新工作也百益无一害。
    看了网上很多大牛写的文章,自己同时也一一对着源码看了,都说好记性不如烂笔头,这才有了这篇文章的由来。

    首先为了不耽误你的时间,我要事先说个明,我这次分析是从Activity开始,platform-29,不是从Framework或者更底层说起,如果大家有兴趣对事件更深层的了解,可以去看看下面这篇文章,它从整个系统层面进行了详细的阐述。

    https://juejin.cn/post/6844903926446161927

    先解释下几个名词
    • 事件序列:Android的每一次按下都会是一连串的事件,而不是单个的,序列主要包括一个按下事件,0个或N个移动事件,一个抬起事件
    • 消费事件:当前view处理完事件,返回值为true,返回false代表不消费

    1 首先来张全局的流程图

    事件分发机制.jpeg
    看上面的图你有没有发现有个逻辑没有讲,就是dispatchTouchEvent返回值代表啥意思?好的,我就从这个切入点讲讲。

    2 对上面的图解析

    先来看Activity的分发代码

    public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
    
    

    我现在写的每一句都不是废话,如果你跟不上,请重复观看代码,我保证你一定跟得上。
    从上面我们可以看出如果getWindow().superDispatchTouchEvent()这个方法返回true,它也就返回true了。否则则调用自己的onTouchEvent()方法。

    这个东东getWindow().superDispatchTouchEvent()又是谁?你要是敢问,我就直接一个眼神给你,然后大家心照不宣。好吧,本着服务大众的心态,你看做人就得心态好。心态好了,吃嘛嘛香,撸码码顺 ╰( ̄▽ ̄)╭
    看过几篇事件分发机制文章的人都应该知道getWindow其实是PhoneWindow,

    getWindow().superDispatchTouchEvent()源码

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

    看到如此清凉的代码,是不是感觉好像在7月炎热的太阳下喝了一瓶透心凉的-雪碧。
    真是简单的不能再简单了。源码都这么简单多好啊!我们接着往下跟

    mDecor.superDispatchTouchEvent()源码

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

    有没有感觉到幸福二重奏?又是让人豁然开朗的一行代码
    这代码真带劲,越看越上瘾。
    到这里,你应该能猜到下个代码会走到哪里了吧?Right,你猜对了,mDecor是一个DecorView---FrameLayout的子类。

    DecorView源码

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    

    于是 ViewGroup的dispatchTouchEvent就进入了我们的视野。
    由于方法过长,我对其进行了裁剪

    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.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // 如果是按下事件,重置一下mFirstTouchTarget这个对象为null
                    cancelAndClearTouchTargets(ev);
                   // 如果是按下事件,重置 拦截Flag
                    resetTouchState();
                }
    
                // 按下或者找到了消费事件的view
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
    //这里先判断是否设置了禁用拦截
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    //没有设置禁用拦截情况下,intercepted取决于onInterceptTouchEvent()返回值
                    if (!disallowIntercept) {
                        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.
    //不是事件流开始的 ACTION_DOWN,也没有事件流的消费组件,那么直接拦截。
                    intercepted = true;
                }
    
                // If intercepted, start normal event dispatch. Also if there is already
                // a view that is handling the gesture, do normal event dispatch.
                if (intercepted || mFirstTouchTarget != null) {
                    ev.setTargetAccessibilityFocus(false);
                }
    
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {// 不取消,不拦截,就分发
            
                                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();
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);// 将消费事件流的子View的父View(当前ViewGroup)记录在消费的链表头
                                    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();
                        }
                        //  dispatchTransformedTouchEvent方法返回false,意味着子View也不消费
                        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;
                        }
                    }// DOWN 事件的处理结束
                }
    
                // Dispatch to touch targets.
                // mFirstTouchTarget赋值是在通过addTouchTarget方法获取的;
                // 只有处理ACTION_DOWN事件,才会进入addTouchTarget方法。
                // 这也正是当View没有消费ACTION_DOWN事件,则不会接收其他MOVE,UP等事件的原因
                if (mFirstTouchTarget == null) { // 子View不消费,交给自己处理
                    // No touch targets so treat this as an ordinary view.
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);// path 1
                } 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;
                    while (target != null) {
                        final TouchTarget next = target.next;
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {// path 2
                            handled = true;
                        } else {
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;
                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;// path 3
                            }
                
                }
    ...
            return handled;
        }
    

    重要部分我都加了注释,依然还有不明白的可以在评论区at我,我知无不言言无不尽。
    重点讲一下我加的3个path,因为从流程图里看不出这三个分支怎么判断的。这里的逻辑判断比较紧凑,防止你跟不上我的车,请先深呼吸一口气,Ready ?Go !

    首先来看

    • path 1 :当没有找到消费的子view时,handled 的值为dispatchTransformedTouchEvent 返回的值,这个方法的第三个参数传的是null值,我们跳进这个方法可以看到,如果child为null,那么就返回super.dispatchTouchEvent(transformedEvent)的值,这个值一定为false,为什么呢?看咱们的前提---当没有找到消费的子view,没有消费说明ViewGroup或子view的onTouchEvent一定返回的是false,所以整个dispatchTouchEvent()返回的值为false,然后它把结果依次往上传递,最后到decorView,然后传到Activity,getWindow().superDispatchTouchEvent()这里返回了false,那么就会走Activity的onTouchEvent,这个方法啥也没干,它也直接返回了false,事件结束。

    • path 2和path 3 :找到了消费事件的view后, handled能不能为true,关键就看else分之了,handled的值如果为true,我们就反推出dispatchTransformedTouchEvent()这个方法也返回true,关键就看这个方法到底返回的值是true还是false呢?这个方法又依赖于View的 dispatchTouchEvent 方法,索性我们就看看它到底藏着什么玄机?
      为了方便分析,我把不同类的方法放一块,我会标出类名

    //ViewGroup.java
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
    ...
          
            // Perform any necessary transformations and dispatch.
            if (child == null) {//如果子view为空,返回父view的dispatchTouchEvent方法的返回值
                handled = super.dispatchTouchEvent(transformedEvent);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                transformedEvent.offsetLocation(offsetX, offsetY);
                if (! child.hasIdentityMatrix()) {
                    transformedEvent.transform(child.getInverseMatrix());
                }
              //如果子view不为空,返回子view的dispatchTouchEvent方法的返回值
                handled = child.dispatchTouchEvent(transformedEvent);
            }
          
            return handled;
        }
    
    // View.java
    public boolean dispatchTouchEvent(MotionEvent event) {
        ...
    
        // mOnTouchListener.onTouch优先于onTouchEvent。
        if (onFilterTouchEventForSecurity(event)) {
            //当存在OnTouchListener,且视图状态为ENABLED时,调用onTouch()方法
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true; //如果已经消费事件,则返回True
            }
            //如果OnTouch()没有消费Touch事件则调用OnTouchEvent()
            if (!result && onTouchEvent(event)) { // [见小节2.5.1]
                result = true; //如果已经消费事件,则返回True
            }
        }
      
        return result;
    }
    

    从view的 dispatchTouchEvent方法可以看出,如果消费了事件,一定是返回true的,这样 handled = child.dispatchTouchEvent(transformedEvent);通过这行代码handled也为true了

    相关文章

      网友评论

          本文标题:ViewGroup 简析dispatchTouchEvent返回

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