说一说android的事件分发机制吧

2020-05-01  本文已影响0人  凤鸣游子

美女

宇宙的演化规则,让时间有了唯一的方向,从过去到未来,从美丽到衰老,而记忆却却不同......

思路

1. 原理囊括: 整体上把握触摸机制的设计目标
2. 重要方法分析: 分析他的实现和细节,即设计的实现过程
3. 局限: 虽然触摸事件机制强大,但是并不完美,而是有一定局限的。

基础原理介绍

  1. 事件先从ViewPostImeInputStage的processPointerEvent开始发送事件, 第一步是发送到DecorView的dispatchPointerEvent,然后便是 DecorView -> Activity -> PhoneWindow -> DecorView这么一个过程哟。

  2. 事件处理的一般过程:

    • 一次事件是指down, move...., up这一系列小事件组成的完整事件。

    • 事件由down开始,从控件树自上而下找到对应能消耗down事件的view, 然后回馈到系统底层, 后续的move, up 事件则会来到他的身上,让他来处理。如果最底层的view或者中间拦截的View都没有消耗down事件,那么后续的move, up事件是不会来到他们身上的。

    • 事件是否被认为消耗,就看他的down事件是否被吃掉 (return true),其他的move, up有没有被吃不关心。意思是只要down返回了true, 中间不发生拦截的话,后面的move, up就算返回了false, 依然还是会继续来到消耗的目标控件上来。

    • 如果发生了拦截, 事件的拦截策略:

      • 传递途中拦截了down, 被拦截的子view将收不到任何事件; 拦截者自己不消耗,那么拦截对象后面也不会有move,up事件,要消耗才会有后续事件呢. 还有一点值得注意,如果在父容器中拦截了down事件,子view申请父容器不要拦截,是不会生效,因为这时候子容器申请的不要拦截策略还没有被系统读取到,一般不要在down中直接拦截!

      • 拦截了move, 子view是不会接收到move事件的,当前拦截对象即使不消耗(false)也没关系,后面的事件也会到他身上来的。

      • 拦截了up, 子View是不会接收up事件的,这时候被拦截子view的点击事件就没法生效的哦!

源码分析

上面的原理介绍,均来自于源码解读和日志,没有源码支撑的一堆罗嗦说出来谁信呀. 源代码版本有点旧,不过简单直白~

1. ViewGroup.dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!onFilterTouchEventForSecurity(ev)) {
            return false;
        }

        final int action = ev.getAction();
    //触摸点在VeiwGroup控件中的x,y位置
        final float xf = ev.getX();
        final float yf = ev.getY();
    //计算viewGroup本身的scroll, 将scroll数值累计到触摸点上,
    //后面计算点是否在控件本身上的时候,当我们viewGroup滚动的时候,子控件的可点击位置要跟随着滚动的内容去变化的,而比较是否在控件内部是与布局边界相比较的,而这边界个又是不变的,因此我们必须要将滚动的数值给补偿回来。
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;

        //检查子view是否请求不要拦截的标记。
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        //当次下发的是down事件, 做的逻辑处理
        if (action == MotionEvent.ACTION_DOWN) {
            //清除前面发生的事件序列的记录消耗目标的target,
            if (mMotionTarget != null) {
                // this is weird, we got a pen down, but we thought it was
                // already down!
                // XXX: We should probably send an ACTION_UP to the current
                // target.
                mMotionTarget = null;
            }
          //在down事件中检查down是否当前ViewGroup发生拦截
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
            //遍历所有的子view, 目的是为了向下传递down事件,直到有人吃掉了。或者寻到view的末尾节点
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    //只有view是visible或者正在执行动画。才会去检测
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {
                        //设置child的边界位置到frame中
                        child.getHitRect(frame);
                        //这里会判断前面计算的触摸点是否在某个child内部。
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            // offset the event to the view's coordinate system
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            //如果在某个child内部就向下分发,这里很重要,view的层级一般都有很多的
                            //这里其实会发生递归调用。当一级ViewGroup往下分发到二级viewgroup的时候
                            //同样会卡在这里往下调用到第三级view,直到找到最末尾的view,判断它是否消耗
                            //然后一层层在这里返回。
                            if (child.dispatchTouchEvent(ev))  {//递归
                                // 当down事件找到了处理目标
                                mMotionTarget = child;
                                return true;
                            }
                            // The event didn't get handled, try the next view.
                            // Don't reset the event's location, it's not
                            // necessary here.
                        }
                    }
                }
            }
        }

        //是否是up, 或者是cancel事件;
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
                (action == MotionEvent.ACTION_CANCEL);
        //如果是up, 会清除前面的禁止拦截标记
        if (isUpOrCancel) {
            // Note, we've already copied the previous state to our local
            // variable, so this takes effect on the next event
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        
        final View target = mMotionTarget;
    //1. 假如前面的down事件没有找到目标,我就自己来处理了,即调用View.dispatchTouchEvent, 这个方法本质是就是调用View.onTouchEvent.
    //2. 或者当前viewGroup拦截了后面的事件,那么我就自己来处理啦。
        if (target == null) {
            // We don't have a target, this means we're handling the
            // event as a regular view.
            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);
        }

    //走到这里来,首先肯定是move,up事件。检测是否发生了拦截
        // if have a target, see if we're allowed to and want to intercept its
        // events
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {//如果move, up发生了拦截
            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);
            //将cancel发给被拦截的子view, 发个告示意思一下,后面大爷来处理来了。
            if (!target.dispatchTouchEvent(ev)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            }
            // clear the target
            //清除本身记住的子view.但是他作为target记录在他的父容器中没有被清除,下次事件就还会
            //发送到他自己身上。
            mMotionTarget = null;
            // Don't dispatch this event to our own view, because we already
            // saw it when intercepting; we just want to give the following
            // event to the normal onTouchEvent().
            return true;
        }

        if (isUpOrCancel) {
            mMotionTarget = null;
        }

        // finally offset the event to the target's coordinate system and
        // dispatch the event.
        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;
        }
        //这里是通常的流程,即没有发生拦截,又有target.就往他身上发事件啦。也是递归地往下发,因为target是一层层的记录的,找到最终的target一般都是view的子类,然后调用他的dispatchTouchEvent, onTouchEvent.
        return target.dispatchTouchEvent(ev);
    }

2. 举个例子吧

前面的核心源码其实有很多的递归调用,理解起来可能有些繞。用例子可能比较好懂些呢。

3. View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
    if (!onFilterTouchEventForSecurity(event)) {
        return false;
    }
    //onTouch来处理啦
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
        mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    //前面不处理或者是false, 我就来处理啦。
    return onTouchEvent(event);
}

/ View.dispatchTouchEvent很简单, 如果onTouch返回了true就不会给onTouchEvent, 否则将事件传递给onTouchEvent, dispatchTouchEvent的返回数值代表的就是onTouchEvent返回的数值。代表着有没有消耗触摸事件。

4. View.onTouchEvent:

该方源码比较简单, 这里就不看了, 简单记录下内容

细节

局限所在

上一篇 下一篇

猜你喜欢

热点阅读