View中消息的传递
对于一颗View树,它的消息的传递是自上而下,即从根节点开始逐层往子类递归传递的。在消息传递的过程中,一旦有View处理了这个消息,那么传递即宣告终止。从这一点看View树的上层有消息的优先处理权。
一、View中TouchEvent的投递流程
事件的处理是通过多种形式的InputStage来分别处理,如NativePostImeInputStage、ViewPostImeInputStage、SyntheticInputStage、EarlyPostImeInputStage。这些都重载了onProcess()方法,以ViewPostImeInputStage为例:
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) {//Pointer事件
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {//Trackball事件
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
sequenceDiagram
InputEventReceiver->>InputEventReceiver:dispatchInputEvent()
InputEventReceiver->>InputEventReceiver:onInputEvent()
InputEventReceiver->>ViewRootImpl: enqueueInputEvent()
ViewRootImpl->> ViewRootImpl:doProcessInputEvents()
ViewRootImpl->> ViewRootImpl:deliverInputEvent()
ViewRootImpl->> InputStage:deliver()
InputStage->>ViewPostImeInputStage:onProcess()
ViewPostImeInputStage->>ViewPostImeInputStage:processPointerEvent()
ViewPostImeInputStage->>ViewPostImeInputStage:processPointerEvent()
ViewPostImeInputStage->>View:dispatchPointerEvent()
View->>View:onTouch()
View->>View:onTouchEvent()
当系统判断当前是SOURCE_CLASS_POINTER类型的事件后,将会调用processPointerEvent()做进一步处理,这个函数会将这一处理权交给View Tree的根元素,即mView的dispatchPointerEvent接口,然后后者再细化判断。
View.java调用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;
}
}
View类事件分发主要考虑两个因素:
-
onTouch
View通过setOnTouchListener来设置一个Event监听。这种方式先与onTouchEvent()
-
onTouchEvent
如果没有设置OnTouchListener或者mViewFlag != ENABLED 又或者onTouch返回false,那么系统会将Event传递给onTouchEvent。
上述两种情况再开发中都经常使用,onTouch更为简洁高效,onTouchEvent()更适用于View扩展类的情况(重载onTouchEvent)。
下面将分段阅读onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
}
上面的情况即便在View disable的情况,也会消耗这个事件,只是不做任何回应而已。
如果View没有被disable,接下来程序将处理这一Touch事件,TouchEvent还可以细分为很多种类型,即ACTION_UP, ACTION_DOWN, ACTION_MOVE和ACTION_CANCEL等。
- ACTION_DOWN
case MotionEvent.ACTION_DOWN:
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
在ACTION_DOWN事件处理中,setPress用于指示View对象是否进入press状态。这样View就可以设计不同press状态下的差别显示(比如Button在press与normal状态下的背景不同)。另外当收到DOWN事件后,View就开始检测它会不会演变成长按事件,可以通过ViewConfiguration.getLongPressTimeout()获取长按时间,在Timeout后系统就需要进行长按处理。如果用户通过setOnLongClickListener设置了响应的函数,那么就会回调这些函数。
- ACTION_MOVE
在按下后并拖动,随后就会产生ACTION_MOVE事件。这个事件随着用户的拖动会不断产生,直到ACTION_UP或者ACTION_CANCEL。
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
pointInView用于判断当前手势是否已经超出了view的范围,如果是,就会移除长按监听的操作并且View对象将退出press状态。
- ACTION_UP
手势操作的结束点,除了改变view的一系列状态,最重要的操作就是判断view受否设置了setOnClickListener所要响应的事件。
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
performClick并不会马上调用执行,而是通过post队列排队的方式去处理。这样做可以让其他View状态优先得到更新处理,以保证执行click操作时这些状态时正确的。
4.ACTION_CANCEL
ACTION_CANCEL比较特殊,并不有用户主动产生,而是有系统谨慎判断后得出结果。这个事件说明当前手势已经被废弃,后续不会有任何和该手势相关的事件产生。
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
二、ViewGroup中TouchEvent的投递流程
ViewGroup与View在接收事件的流程上基本一致,因为ViewGroup要对子对象进行处理,具体实现为ViewGroup重载View中的dispatchTouchEvent方法,对View提供的派发机制进行重新规划。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;//event是否被处理
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/*Step1*/
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
/*Step2*/
// 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;
}
}
step1: Down事件是后续事件的起点,所以一旦收到ACTION_DOWN程序就会清理以前的状态cancelAndClearTouchTargets(ev)和resetTouchState();
step2: 变量intercepted代表是否拦截事件。
- actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
如果是DOWN事件,或者mFirstTouchTarget不为空,分为两种情况
1.1、 disallowIntercept 为false
此时ViewGroup允许拦截,就需要通过intercepted =onInterceptTouchEvent(ev)来判断是否要真正执行拦截。
1.2、disallowIntercept 为true
ViewGroup不允许拦截
2.如果不是DOWN事件,而且mFirstTouchTarget为空,那么intercepted为true,表示ViewGroup选择继续拦截事件。
step3:如果intercepted为false,表明ViewGroup不希望拦截这一消息
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 = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
}
}
}
执行归属判断的是canReceivePointerEvents和isTransformedTouchPointInView。前者表示这个child是否能接受Pointer Events;后者计算(x, y)这个点是否落在child的范围内。如果找到了事件的归属者,接下来就将事件投递给它,实现函数为dispatchTransformedTouchEvent。如果child != null这个函数会调用child的dispatchTouchEvent。如果child == null 就会有当前ViewGroup处理,调用super.dispatchTouchEvent()由于ViewGroup也是View。
总结:
消息的处理过程的理解
ViewGroup直接拦截intercept消息,起决定作用的是onInterceptTouchEvent。Android建议继承ViewGroup时只重载onInterceptTouchEvent,而不是重载dispatchTouchEvent。因为后者是所有ViewGroup共性的提取,不应轻易改变;前者是体现所有ViewGroup差异的地方。
-
当onInterceptTouchEvent返回false时,说明当前的ViewGroup并没有拦截这个事件,所以它需要继续往下传递,知道找到处理的View.
-
当onInterceptTouchEvent返回true时,说明当前ViewGroup需要拦截这个事件以供内部处理