流程分析由Activity开始,前面不分析。
触碰事件首先发放到Activity,Activity再转发给它依赖的Window(也就是PhoneWindow对象),Window转发给它所持有的DecorView(也就是顶层FrameLayout对象),由此开始就是经常说的ViewGroup与View的触碰事件传递流程了。
把上面的流程分两部分分析,第一部分是Activity到DecorView(简单);第二部分是ViewGroup与View流程(复杂)。
Activity:
public boolean dispatchTrackballEvent(MotionEvent ev) {
if (getWindow().superDispatchTrackballEvent(ev)) {
return true;
}
return onTrackballEvent(ev);
}
getWindow的实现对象是PhoneWindow。
PhoneWindow:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor是顶层布局DecorView对象,而DecorView对象继承自FrameLayout布局对象。
DecorView:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
...
}
DecorView的superDispatchTouchEvent方法还是调用的super.dispatchTouchEvent,其实是调用到了ViewGroup中。有此处往后就进入了ViewGroup的分发流程了。
对ViewGroup的dispatchTouchEvent方法进行分段分析。
ViewGroup:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
第一段
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
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.
intercepted = true;
}
}
检测拦截状态。if判断条件actionMasked==MotionEvent.ACTION_DOWN表示是按下事件,mFirstTouchTarget != null表示有它的子控件消费了事件,即if(Down事件 或 Move/Up同时Down时有事件消费了它),这是获取ViewGroup的FLAG_DISALLOW_INTERCEPT标识,如果时true,表示它的子控件不希望父容器拦截事件,反之就调用onInterceptTouchEvent拦截判断方法。
最后一个判断条件是说Move/Up事件时没有子控件消费之前的Down事件,就设置拦截。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
第二段
if (!canceled && !intercepted) {
......
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
......
}
只有当此次事件未被取消未被拦截的情况才进行第二段的代码,否则略过。
并且只有Down事件才会进入。
也就是未拦截情况下的Down事件,就会执行第二段。
接着for循环遍历所有子控件,将不符合条件或不在点击范围内的子控件跳过。剩下符合条件的事件会调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
ViewGroup:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
......
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
......
}
其实就是根据参数child是否为空去调用父类或child的dispatchTouchEvent分发。
如果分发后有子控件消费了事件就会返回true,然后进入分支调用addTouchTarget(child, idBitsToAssign)方法
ViewGroup:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
这里会先用子控件child构造一个TouchTarget对象,TouchTarget是一个链表结构,下面的操作是将mFirstTouchTarget链接到新构造的TouchTarget后面,并将mFirstTouchTarget指向新构造的TouchTarget。
ViewGroup:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
第三部分
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = 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;
}
}
}
mFirstTouchTarget == null说明没有子控件消费事件,此时调用dispatchTransformedTouchEvent(ev, canceled, null, ALL_POINTER_IDS),注意此处的chid参数是null,也就是调用ViewGroup的super.dispatchTouchEvent,最终调用View的dispatchTouchEvent,而View的此方法里会调用View的onTouchEvent方法。由前面的分析知道,只有Down事件才能进入第二段的判断,而mFirstTouchTarget是在第二段代码里Down事件消费了才会赋值的,也就是说没有消费Down事件的后序事件会进入此处的判断里,简而言之,Down事件没有人消费的话,后面的MOVE/UP事件在顶层DecorView里就会被打回去,不会向下分发。总结:处理了未消费的Down事件和后序的MOVE/UP事件。
mFirstTouchTarget != null说明有子控件消费Down事件,接着循环mFirstTouchTarget链表,在循环里有个判断,if(alreadyDispatchedToNewTouchTarget && target == newTouchTarget)是说明此次是Down事件并且事件被消费了,直接设置dispatchTouchEvent返回值为true;else的情况就是Down被消费后的MOVE/UP事件,这里又调用了dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)并且child参数是target.child(即消费了Down事件的子控件),意思是把MOVE/UP直接交给了消费了Down事件的子控件去了。总结:处理了消费了的Down 事件(即不做什么处理)和后序的MOVE/UP事件(直接交给消费Down事件的子控件处理)。
ViewGroup的分发分为上面的三步走,暂不考虑子控件主动设置FLAG_DISALLOW_INTERCEPT。
第一步,设置intercepted,分成两份,一份是Down事件和消费Down事件的后序事件MOVE/UP,执行onInterceptTouchEvent方法设置intercepted;一份是未消费Down事件的后序事件MOVE/UP,intercepted=false。
第二步,只有未拦截的Down事件才能进入这部分,遍历所有子控件找到符合的子控件调用child.dispatchTouchEvent。这是一个不断迭代的过程,ViewGroup不断迭代子ViewGroup的dispatchTouchEvent方法,直到有控件消费了Down事件,这时就在ViewGroup里把这个子控件保存起来。
第三步,根据第二步保存的子控件链表mFirstTouchTarget是否null,分成两部分,
**(1)**是==null,未消费的Down事件或Down未消费的后序事件MOVE/UP,这时调用ViewGroup的super.dispatchTouchEvent方法。
**(2)**是!=null,消费的Down事件或Down消费的后序事件MOVE/UP,这时Down事件直接设置方法返回值true,MOVE/UP事件就去找到消费Down事件的子控件,然后调用子控件的dispatchTouchEvent方法。
其实总是说Android的触碰事件在View树里的传递流程是一个U字型,由左边用dispatchTouchEvent不断往下分发,再由右边用onTouchEvent不断往上输送,上面代码第二段负责左边,第三段负责右边。