Anroid触摸事件分发机制---ViewGroup相关

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yinzhijiezhan/article/details/84632808

这篇文章只介绍ViewGroup事件分发机制,因为Activity和View的事件分发,相对很简单,网上随便找一篇文章基本就能理解了,但是对于ViewGroup的事件分发机制,我真的看了不下20篇文章,没有一篇讲的到位的,没有一篇告诉你重点关注哪个点,最后只是告诉你一堆结论,或者给你看一堆流程图,最气人的是没有一篇文章告诉你ACTION_CANCEL怎么回事,ACTION_CANCEL时系统其实做了一个很重要的操作,这个操作你理解了,你之前的疑惑也许就都 理解了,后面会提到,其实对于ViewGroup的分发源码,你看Android2.3的源码就可以了,真的很简单,甚至让你看了后一辈子都忘不了,肯本不需要记任何结论或画任何复杂的流程图,高版本的源码太过复杂但是原理和Android2.3基本一样。

重点要关注的点,我会在涉及到的时候强调。

一:Android事件分发整体流程简图

这是个关注点

这个图很重要,可能你也懂这个图,但是我只是想提醒你看源码的时候心里想着这个图,这样逻辑才清晰。

二:ViewGroup事件分发Anroid2.3源码

我先把源码帖出来,这个源码跟高版本相比已经很简单了,但是我们看的时候还是可以简化一些,我下面会贴出重点代码

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                    child.getHitRect(frame);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }
        return super.dispatchTouchEvent(ev);
    }
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
        }
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        mMotionTarget = null;
    }
    return target.dispatchTouchEvent(ev);
}


上面的代码我们再简化一下,并做 一些说明:


pirvate View mMotionTarget;
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    //是否允许拦截
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (action == MotionEvent.ACTION_DOWN) {//只有ACTION_DOWN时才走这个if语句
      //ACTION_DOWN时重置mMotionTarget为null,mMotionTarget指向处理并且消费掉当前ACTION_DOWN的子View。
      //如果不为null说明指向上次处理并消费了ACTION_DOWN的子View,因此要重置为null
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        //disallowIntercept默认为false,可通过requestDisallowInterceptTouchEvent(true)修改其值
        //高版本anroid中我有看到,在ACTION_DOWN时有一个对FLAG_DISALLOW_INTERCEPT重置的操作,避免requestDisallowInterceptTouchEvent(true)造成的影响
        //所以我在这里加这句代码和高版本一致
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        //不拦截有两种情况:1,不允许拦截;2,虽然允许拦截,但是并没有拦截。
        //既然不拦截就找对应的子View处理这个ACTION_DOWN.就是for循环判断 点击区域落到哪个子View身上。
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            for (所有子View) {
                final View child = 找到的那个可以接收ACTION_DOWN的子View;
                if (child.dispatchTouchEvent(ev))  {
                  //交给这个子View处理,如果子View消费掉了,就让mMotionTarget指向这个子View,且直接return,本次ACTION_DOWN事件分发结束。
                  //如果这个子View虽然处理了但是没有消费掉该ACTION_DOWN,则继续向下执行。
                    mMotionTarget = child;//唯一给mMotionTarget赋值的地方,谨记!!!
                    return true;
                }
            }
            //没有找到合适的子View处理该ACTION_DOWN,例如点击了空白区域,即没有子View的区域等等。继续向下执行
        }
    }
    //targt和mMotionTarget指向同一个对象,需要注意的是,只有在ACTION_DOWN时ViewGroup不拦截且子View消费掉ACTION_DOWN时,才给mMotionTarget,只有这一个地方,谨记!!!
    final View target = mMotionTarget;
    //target为null,说明没有可接收事件的子View,需要ViewGroup自己处理该事件
    //target为null的原因有很多:
    //1,ACTION_DOWN时,可能是之前的逻辑中onInterceptTouchEvent()返回true即拦截了,或者虽然没拦截但是没有找到合适的子View处理该事件,
    //或者虽然找到子View处理该事件了但是子View没有消费掉该事件,这些都导致mMotionTarget没有被赋值(参考前面的for循环部分)。
    //2,非ACTION_DOWN时,则说明之前的事件序列都是ViewGroup自己处理的(参考1)或者之前ViewGroup拦截了某次事件(后面的代码可看到拦截时会将mMotionTarget置为null)
    if (target == null) {
      //ViewGroup自己处理事件的逻辑
        return super.dispatchTouchEvent(ev);//若执行了这里则后续代码不再执行
    }
    //能走到这里的不可能是ACTION_DOWN,且target不是null,说明子View有处理能力同时ViewGroup也可以拦截,体会一下。
    //如果拦截则给子View(target)一个ACTION_CANCEL事件,同时mMotionTarget置为null,本次分发结束,下次分发的事件就交给ViewGroup自己处理,
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        if (!target.dispatchTouchEvent(ev)) {
        }
        //下面两行可看到,所谓的拦截其实并不是拦截,而是让子View执行ACTION_CANCEL事件,但是ViewGroup并不处理拦截下来的这个事件,而是直接消费掉(true),只是说下次事件
        //由我ViewGroup来处理。
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    //如果不拦截,则由子View处理
    return target.dispatchTouchEvent(ev);
}

这段代码你肯定看的懂,没必要告诉你一堆结论,给你一堆流程图,记结论很累,你的理解就是结论。

这里并没有详细介绍onInterceptTouchEvent(ev)和requestDisallowInterceptTouchEvent(true)两个方法,这两个方法很好理解,也有很多文章介绍,,简单说下就是:

requestDisallowInterceptTouchEvent(true)用于修改FLAG_DISALLOW_INTERCEPT标记为,为true说明禁止Viewgroup拦截,这样也就不会调用onInterceptTouchEvent(ev)判断是否拦截了,为false说明允许拦截,注意,允许拦截不代表肯定拦截,还要调用onInterceptTouchEvent(ev)判断最终拦截不拦截。

关注点*:再次强调看源码时一定要结合事件分发大体过程看,结合最上面的图看,脑子里要有这个分发的过程,提醒 自己时间是随着手指边移动边不断分发的。且是不断调用dispatchTouchEvent(MotionEvent ev) 的

关注点1:mMotionTarget默认为null,唯一给它赋值的地方在有子View消费掉ACTION_DOWN时,注意是唯一。

关注点2:target就是mMotionTarget

关注点3:ViewGroup拦截子View的某个非ACTION_DOWN事件时,会给这个子View一个ACTION_CANCEL事件同时把mMotionTarget置为null,这样下次分发的事件就由ViewGroup处理了。因为mMotionTarget为null了,这个事件序列之后的事件就全部交给ViewGroup处理了。

如果对您有用,请打赏仨瓜俩枣的!

  

猜你喜欢

转载自blog.csdn.net/yinzhijiezhan/article/details/84632808
今日推荐