上一篇文章我们说到当输入事件从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
指向当前界面的顶层布局DecorView
,handled
作为此次事件分发的结果返回,
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
,接着会调用Activity
的dispatchTouchEvent
进一步分发事件:
Activity.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
onUserInteraction
在Activity
中是个空方法,它的作用应该是告诉开发者此Activity
接收到事件了,接着就会调用PhoneWindow
的superDispatchTouchEvent
方法,其返回值代表目标View对事件的处理结果,我们可以看到如果目标View没有消费掉此次事件(即返回值为false)则会调用Activity
的onTouchEvent
来处理。
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最后才有资格拿到事件进行处理。
我们现在来对此方法进行总结:
- 首先会调用
onFilterTouchEventForSecurity
方法对此次事件进行初步判断,判断的是ViewGroup
是否被遮挡或者隐藏。 - 步骤1的条件判断通过之后,接着对于
ACTION_DOWN
事件会清空上一个事件序列(一个事件序列通常由一个ACTION_DOWN
,N个ACTION_MOVE
,一个ACTION_UP
组成)留下的各种状态,最主要是清空TouchTarget
链表。 - 接着会有两个条件来判断是否走
ViewGroup
的拦截机制,条件1:此次事件是否为ACTION_DOWN
,条件2:mFirstTouchTarget
是否为空,这两个条件的意思是对于一个事件序列的ACTION_DOWN
事件一定会走ViewGroup
的拦截机制,并且同一事件序列的一个事件如果被拦截了,那么后续事件默认都会被拦截而不会再走拦截方法onInterceptTouchEvent
,子View可以通过requestDisallowInterceptTouchEvent
方法请求父View不要拦截。 - 接着又会两个条件来判断事件事件继续分发,
canceled
和intercepted
,事件被取消和被拦截,其实canceled
多半是因为intercepted
导致的,这个后面再说。 - 对于没有被取消且没有被拦截,且是
ACTION_DOWN
的事件就要开始遍历View树找到真正消费事件的子View了,这里为何会单独对ACTION_DOWN
进行判断呢?这是因为一个事件序列可能包含多个触摸事件,而触摸事件寻找消费的子View是通过递归遍历View树,为了性能考虑,Android的设计为:当接收到ACTION_DOWN
时开始对View树进行遍历,找到最终消费事件的子View之后将其保存,同一事件序列的后续ACTION_MOVE
,ACTION_UP
则不再需要遍历,直接将事件发送给保存好的子View就行了,对子View的保存就用到了TouchTarget
,这是一种链表结构,后面再说。 - 对于目标子View的寻找就比较简单了,首先将当前
ViewGroup
的所有子View以Z-order的顺序进行重建,保存在一个list中,然后遍历list,从Z-order最大的子View开始,遍历条件有两个:当前遍历的子View可以接收事件,并且触摸区域落在当前子View之内则说明成功找到子View,然后调用dispatchTransformedTouchEvent
执行子View事件处理流程,如果事件成功处理则会为此子View构建TouchTarget
,并赋值给mFirstTouchTarget
。 - 接着对于
mFirstTouchTarget
不为空的情况会遍历链表,其目的是步骤6已经找到接收事件的目标子View并且保存到了TouchTarget
链表,对于一个事件序列的后续事件只需要遍历链表分发事件就行了。 - 对于没有找到消费事件的子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
:
第二次构建TouchTarget
:
以此类推,mFirstTouchTarget
总是指向最新构建的TouchTarget
:
对于上面的布局结构,如果我们点击Button
,事件的分发就是从DecorView
->ViewGroup A
->ViewGroup B
->ViewGroup C
->Button
,mFirstTouchTarget
最终作为头指针指向事件分发流程的所有View所构建的TouchTarget
链表,有了TouchTarget
链表,只需要在事件序列开始即ACTION_DOWN
到来时进行一次View树遍历,后续的ACTION_DOWN
,ACTION_UP
就可以直接通过TouchTarget
链表进行分发。
事件拦截onInterceptTouchEvent
我们继续来看事件拦截,ViewGroup提供了onInterceptTouchEvent
用来对事件进行拦截,默认情况父View是不会拦截事件的,什么情况下需要拦截呢?
比如这种情况,一个ListView中嵌套了多个Button,当我们点击Button进行上下滑动操作时,此时我们并不希望Button来处理事件,而是希望ListView处理,而我们如果仅仅是点击Button,此时又希望Button来处理事件,这时就需要ListView有条件的对事件进行拦截,比如判断用户滑动的距离,速度等。
对于事件拦截的使用很简单,只需要重写ViewGroup
的onInterceptTouchEvent
方法:
public boolean onInterceptTouchEvent(MotionEvent ev) {
....
return true;
}
返回true就能实现拦截,事件的拦截同样是针对一个事件序列来说的,当一个事件序列的事件被父View拦截,那么此事件序列的后续事件子View都收不到,这就引出一个问题,像Button
这样的控件是需要收到ACTION_UP
才能对事件进行处理的,因为Button
需要判断当前事件是点击还是长按,如果在ACTION_MOVE
时Button
的事件被拦截掉,那么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
,对于事件处理的顺序就是:onTouch
,onTouchEvent
,onClick
。