AndroidR Input子系统(10)View的事件分发机制

上一篇文章我们说到当输入事件从InputDispatcher通过server端InputChannel将输入事件发送给应用程序的client端,其实最终事件被发送到了java层ViewRootImpl中,此类定义了多种类型的InputStage,以责任链模式进行处理分发输入事件。

final class SyntheticInputStage extends InputStage{}
final class ViewPostImeInputStage extends InputStage{}
final class NativePostImeInputStage extends AsyncInputStage{}
final class EarlyPostImeInputStage extends InputStage{}
final class ImeInputStage extends AsyncInputStage{}
final class ViewPreImeInputStage extends InputStage{}
final class NativePreImeInputStage extends AsyncInputStage{}

对于View来说主要使用ViewPostImeInputStage这种类型的InputStage来处理触摸事件,我们直接来看这个类:

/**
     * Delivers post-ime input events to the view hierarchy.
     */
    final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
  }

onProcess方法用于输入事件的具体处理,此方法中将按键事件和触摸事件分开,本篇文章主要研究触摸事件的流程,对于触摸事件,根据事件源分了三个方法,普通的触摸事件由processPointerEvent处理。

ViewPostImeInputStage.processPointerEvent

private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            ...
            boolean handled = mView.dispatchPointerEvent(event);
            ...
            return handled ? FINISH_HANDLED : FORWARD;
        }

mView指向当前界面的顶层布局DecorViewhandled作为此次事件分发的结果返回,
DecorView并没有重写父类的dispatchPointerEvent方法,所以接着到View中看此方法实现。

View.dispatchPointerEvent

@UnsupportedAppUsage
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

针对Touch事件又会调dispatchTouchEvent方法,此方法在DecorView中有重写,

DecorView.dispatchTouchEvent

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

这里的Window.Callback指向当前Activity,在Activity启动时会调用自己的attach方法,此方法中会将自己作为callback传给window,

final void attach(......){
    ...
	mWindow.setCallback(this);
	...
}

所以上述mWindow.getCallback()获取的就是当前启动的Activity,接着会调用ActivitydispatchTouchEvent进一步分发事件:

Activity.dispatchTouchEvent

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

onUserInteractionActivity中是个空方法,它的作用应该是告诉开发者此Activity接收到事件了,接着就会调用PhoneWindowsuperDispatchTouchEvent方法,其返回值代表目标View对事件的处理结果,我们可以看到如果目标View没有消费掉此次事件(即返回值为false)则会调用ActivityonTouchEvent来处理。

PhoneWindow.superDispatchTouchEvent

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

DecorView.superDispatchTouchEvent

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

这里调用super.dispatchTouchEvent最后到了ViewGroup中,此时才真正开始View的事件分发,我们可以看到输入事件从native层到ViewRootImpl之后会经过:DecorView->Activity->PhoneWindow->DecorView->ViewGroup的流程。

ViewGroup可以理解为View事件分发的起始点,事件分发机制其实分为两部分,一部分为事件的下发,一部分为事件处理结果的上报。
事件下发的目的是找到处理该事件的目标View,事件上报的目的是将最终的处理结果返回给ViewPostImeInputStage

private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            ...
            boolean handled = mView.dispatchPointerEvent(event);
            ...
            return handled ? FINISH_HANDLED : FORWARD;
        }

即上述方法中的handled就是事件处理上报的最终结果,有了这个结果就可以确定此次事件是朝着InputStage责任链的下一环继续分发还是就此结束。

ViewGroup.dispatchTouchEvent

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  		......
  		boolean handled = false;
  		//判断此次触摸事件是否被过滤掉,条件由两个flag决定,FILTER_TOUCHES_WHEN_OBSCURED
  		//和MotionEvent.FLAG_WINDOW_IS_OBSCURED,这两个flag用来表示
  		//当前接收触摸事件的View是否被遮挡或者隐藏,只有未被遮挡或隐藏才能
  		//进一步处理事件。
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //当ACTION_DOWN事件到来时,会清除并重置之前设置的各种状态,
                //这是因为Android的事件分发是以一个事件序列为单位进行分发
                //和拦截,当一个此次事件为ACTION_DOWN则表明这是一个新的
                //事件序列,所以需要清空和重置上一个事件序列的状态
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            //代表此次事件是否被拦截
            final boolean intercepted;
            //两种情况下会走进事件拦截的流程,1.此次事件为ACTION_DOWN,
            //2.mFirstTouchTarget != null,mFirstTouchTarget描述的是
            //接收此次事件的目标
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //子View是否有请求过父View不要拦截事件,通过调用父View的
                //requestDisallowInterceptTouchEvent方法来请求
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //如果子View没有请求View不要拦截事件,则走正常
                    //父View事件拦截流程
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                //对于非ACTION_DOWN事件并且mFirstTouchTarget为空则直接拦截
                //此次事件序列的后续事件
                intercepted = true;
            }

            ...

            //检查是否需要取消事件,由resetCancelNextUpFlag或者事件本身
            //决定,resetCancelNextUpFlag的实现很简单,对于添加了
            //PFLAG_CANCEL_NEXT_UP_EVENT的View清空此状态并返回true,
            //这个PFLAG_CANCEL_NEXT_UP_EVENT的含义从注释字面意思看
            //是View和ViewGroup处于分离状态
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
            
            	......
            //记录接收事件的目标
            TouchTarget newTouchTarget = null;
            //记录是否成功分发事件的状态值
            boolean alreadyDispatchedToNewTouchTarget = false;
            //如果事件没有被取消并且没有被拦截
            if (!canceled && !intercepted) {
                ...
                //ACTION_POINTER_DOWN和ACTION_HOVER_MOVE是多点触控与
                //鼠标相关的事件本篇不讨论,这里只看ACTION_DOWN会走进如下分支
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        
                    //多点触控相关的逻辑,省略...

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                        //这里会将ViewGroup所有的子View重新以Z-order的顺序
                        //从小到大排列,返回一个list
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        //子View是否自定义绘制顺序,一般情况都为false
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //对所有子View进行遍历
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //对于没有自定义子View绘制顺序的情况,
                            //childIndex就等于i
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            
                            //这里会从前面得到的根据Z-order排好序的preorderedList
                            //中获取View,并且是从Z-order最大的View开始遍历
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                //如果此View无法接收事件或者当前事件的
                                //落点不在这个View区域内则返回进行下一轮循环
                                continue;
                            }
                            //走到这里说明当前View既能接收事件,并且
                            //事件也落在View内,接着就需要进一步处理事件了

                            //对于单点触控事件,newTouchTarget此时为空
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            //如果有设置PFLAG_CANCEL_NEXT_UP_EVENT,在此清除
                            resetCancelNextUpFlag(child);
                            //这里就会执行子View事件分发处理逻辑了,待后面详细分析      
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                //走进来说明子View成功消费事件
                                ....
                                
                                //为消费此次事件的子View构建TouchTarget,
                                //并且会将构建的TouchTarget赋值给mFirstTouchTarget
                                //和newTouchTarget,对于单点触控来说
                                //TouchTarget链表只有mFirstTouchTarget一个元素
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //alreadyDispatchedToNewTouchTarget赋值为true
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                         ...
                        }
                        //清空preorderedList
                        if (preorderedList != null) preorderedList.clear();
                    }
                    ...
                }
            }

            if (mFirstTouchTarget == null) {
                //mFirstTouchTarget为空说明没有子View接收此次事件
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                //遍历TouchTarget链表,因为mFirstTouchTarget不为空,所以已经找到目标,只需要进行事件分发即可
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                //如果事件被取消或者一个事件序列结束就需要重置事件的各种状态,
                //最重要的状态就是将mFirstTouchTarget置空
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                //多点触控相关的收尾
				...
            }
        } 

        if (!handled && mInputEventConsistencyVerifier != null) {
            //事件未被处理,回调通知
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
  	......
}

ViewGroup的dispatchTouchEvent比较长,但是逻辑不算难,上述方法我移除了鼠标相关和多点触控相关的代码,就单点触控的事件分发来看逻辑很简单,很多代码其实都是对分发的条件判断,比如某个子View想要接收事件它起码得不被遮挡吧,然后你触摸的地方得在这个View的边界内吧,这个View还得处于Z-order的最顶层等,只有经过各种条件的判断这个View最后才有资格拿到事件进行处理。

我们现在来对此方法进行总结:

  1. 首先会调用onFilterTouchEventForSecurity方法对此次事件进行初步判断,判断的是ViewGroup是否被遮挡或者隐藏。
  2. 步骤1的条件判断通过之后,接着对于ACTION_DOWN事件会清空上一个事件序列(一个事件序列通常由一个ACTION_DOWN,N个ACTION_MOVE,一个ACTION_UP组成)留下的各种状态,最主要是清空TouchTarget链表。
  3. 接着会有两个条件来判断是否走ViewGroup的拦截机制,条件1:此次事件是否为ACTION_DOWN,条件2:mFirstTouchTarget是否为空,这两个条件的意思是对于一个事件序列的ACTION_DOWN事件一定会走ViewGroup的拦截机制,并且同一事件序列的一个事件如果被拦截了,那么后续事件默认都会被拦截而不会再走拦截方法onInterceptTouchEvent,子View可以通过requestDisallowInterceptTouchEvent方法请求父View不要拦截。
  4. 接着又会两个条件来判断事件事件继续分发,canceledintercepted,事件被取消和被拦截,其实canceled多半是因为intercepted导致的,这个后面再说。
  5. 对于没有被取消且没有被拦截,且是ACTION_DOWN的事件就要开始遍历View树找到真正消费事件的子View了,这里为何会单独对ACTION_DOWN进行判断呢?这是因为一个事件序列可能包含多个触摸事件,而触摸事件寻找消费的子View是通过递归遍历View树,为了性能考虑,Android的设计为:当接收到ACTION_DOWN时开始对View树进行遍历,找到最终消费事件的子View之后将其保存,同一事件序列的后续ACTION_MOVEACTION_UP则不再需要遍历,直接将事件发送给保存好的子View就行了,对子View的保存就用到了TouchTarget,这是一种链表结构,后面再说。
  6. 对于目标子View的寻找就比较简单了,首先将当前ViewGroup的所有子View以Z-order的顺序进行重建,保存在一个list中,然后遍历list,从Z-order最大的子View开始,遍历条件有两个:当前遍历的子View可以接收事件,并且触摸区域落在当前子View之内则说明成功找到子View,然后调用dispatchTransformedTouchEvent执行子View事件处理流程,如果事件成功处理则会为此子View构建TouchTarget,并赋值给mFirstTouchTarget
  7. 接着对于mFirstTouchTarget不为空的情况会遍历链表,其目的是步骤6已经找到接收事件的目标子View并且保存到了TouchTarget链表,对于一个事件序列的后续事件只需要遍历链表分发事件就行了。
  8. 对于没有找到消费事件的子View即mFirstTouchTarget为空,以及事件被取消的情况会做一些收尾工作。

上面的总结主要对于单点触控的情况,如果是多点触控还要复杂一些。

我们接着来理解TouchTarget,这个数据结构对于事件分发非常重要,TouchTarget在整个ViewGroup事件分发中是以链表的形式存在,它保存了一个ViewGroup中接收事件的目标子View。

TouchTarget

   private TouchTarget mFirstTouchTarget;
   private static final class TouchTarget {
        ...
        //保存接收事件的一级View
        public View child;
        ...
        //
        public TouchTarget next;

        @UnsupportedAppUsage
        private TouchTarget() {
        }
        ...
   }

ViewGroup中只有一个地方会构造TouchTarget,就是在接收到ACTION_DOWN时,此时会遍历子View并找到目标子View,且目标子View成功处理事件之后,就会为此子View构建对应TouchTarget,而多个ViewGroup的多个目标子View就构成了TouchTarget事件分发的链表:

 private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

这种代码就是典型的链表,mFirstTouchTarget作为链表的头指针,我们用图来表示,第一次构建TouchTarget
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210424155759439.png
第二次构建TouchTarget

以此类推,mFirstTouchTarget总是指向最新构建的TouchTarget
在这里插入图片描述

对于上面的布局结构,如果我们点击Button,事件的分发就是从DecorView->ViewGroup A->ViewGroup B->ViewGroup C->ButtonmFirstTouchTarget最终作为头指针指向事件分发流程的所有View所构建的TouchTarget链表,有了TouchTarget链表,只需要在事件序列开始即ACTION_DOWN到来时进行一次View树遍历,后续的ACTION_DOWNACTION_UP就可以直接通过TouchTarget链表进行分发。

事件拦截onInterceptTouchEvent

我们继续来看事件拦截,ViewGroup提供了onInterceptTouchEvent用来对事件进行拦截,默认情况父View是不会拦截事件的,什么情况下需要拦截呢?
在这里插入图片描述
比如这种情况,一个ListView中嵌套了多个Button,当我们点击Button进行上下滑动操作时,此时我们并不希望Button来处理事件,而是希望ListView处理,而我们如果仅仅是点击Button,此时又希望Button来处理事件,这时就需要ListView有条件的对事件进行拦截,比如判断用户滑动的距离,速度等。

对于事件拦截的使用很简单,只需要重写ViewGrouponInterceptTouchEvent方法:

public boolean onInterceptTouchEvent(MotionEvent ev) {
        ....
        return true;
    }

返回true就能实现拦截,事件的拦截同样是针对一个事件序列来说的,当一个事件序列的事件被父View拦截,那么此事件序列的后续事件子View都收不到,这就引出一个问题,像Button这样的控件是需要收到ACTION_UP才能对事件进行处理的,因为Button需要判断当前事件是点击还是长按,如果在ACTION_MOVEButton的事件被拦截掉,那么Button同样收不到后续的ACTION_UP事件,这个时候就需要引入一种新的事件用于告诉被拦截掉事件的子控件,它就是ACTION_CANCEL,有了ACTION_CANCEL就可以对事件被拦截掉的情况做收尾工作。

子View事件处理dispatchTouchEvent

对于事件的具体处理就比较简单了,

public boolean dispatchTouchEvent(MotionEvent event) {
        ....
        
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ....

        return result;
    }

首先判断该View是否设置了触摸监听,即是否调用过setOnTouchListener方法,如果有则将事件传入其onTouch方法进行处理并返回结果,如果没有设置,则会调用View的onTouchEvent方法,此方法中会有条件的调用onClick,对于事件处理的顺序就是:onTouchonTouchEventonClick

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值