事件的分发流程
Activity->window->ViewGroup(decor View)
dispathTouchEvent(属于Activity)
决定是否Activity要自己消费事件,首先将事件交由依附Activity的Window进行分发,如果View消费了事件,返回true,那么分发过程结束,如果所有的View都没有消费事件,那么Avtivity的onTouchEvent()会被调用.
decorView 分发过程
顶级View(ViewGroup)对View的分发包括几个过程:
点击事件到达-> 调用dispatchTouchEvent()->如果interecptTouchEvent()返回true,消费事件->a 否则 ->b
a.如果有onTouchListener(),则 onTouch()会被触发,否则onTouchEvent()会被调用,如果在onTouchEvent中设置了onClickListener,onCLick会被调用.
b.事件会传递给事件链上的子View,子View接收到事件调用dispatchTouchEvent().
事件链是一开始ViewGroup就会遍历所有的子View来判断哪些View可以接受到事件,主要根据 子View是否在播动画 和 点击坐标是否位于子View上.
interecptTouchEvent
并非每次都会调用,只有当 事件是ACTION_DOWN 且 mFirstTouchTarget != null(交由子View处理) 且 FLAG_DISALLOW_INTERCEPT 没被设置 时调用 onInterecptTouchEvent.
因为ACTION_DOWN基本上都是一个新的事件,这个时候肯定没有交由子View来处理,所以mFirstTouchTarget == null,再加上前面就有代码对FLAG_DISALLOW_INTERCEPT进行初始化,所以如果是ACTION_DOWN,onInterecptTouchEvent一定会被调用,但是decorView(Viewgroup)默认都不会拦截事件.因此子View不能影响decorView对ACTION_DOWN的处理.
如果 事件是ACTION_DOWN 且 mFirstTouchTarget != null(交由当前View处理) 都不成立,即是收到ACTION_MOVE,ACTION_UP且已经标记不拦截事件,交由子View处理了就直接交由子View,不再拦截.
当消费事件返回true,不消费返回false.
public boolean dispatchTouchEvent(MotionEvent ev) {
...
/**
见 上一节 `interecptTouchEvent`
*/
// 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;
}
...
/**
分发之核心,遍历所有的子View来判断哪些View可以接受到事件,主要根据 子View是否在播动画 和 点击坐标是否位于子View上.
*/
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
...
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
/**
如果所有的View都没有消费事件,那么ViewGroup将会自己消费事件;
否则继续处理后续事件(ACTION_UP,ACTION_MOVE)的分发,还有可能出现的取消点击(在屏幕上滑动离开目标View)
不再判断,直接将点击事件传给目标View
*/
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
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;
}
}
...
}
View对点击事件的处理
以上都是点击事件怎么从Activity->Window->decor View
但是大多数情况下,处理点击事件的是View.
具体过程已经写了注释:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
/**
这里是View处于不可DISABLE的状态,但是仍然会消费事件.
前提是view是CLICKABLE 或者 LONG_CLICKABLE
*/
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
/**
如果View有设置代理,会执行mTouchDelegate.onTouchEvent(event)
*/
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
/**
如果View是CLICKABLE或者LONG_CLICKABLE,就消费事件.
*/
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
/**
如果`ACTION_UP`,说明一次点击事件结束,`performClick()`方法被调用,它调用它内部的`click()`方法.
*/
case MotionEvent.ACTION_UP:
...
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
...
break;
/**
如果是`ACTION_DOWN`的情况,考虑是不是滑动操作.如果是就进行滑动操作的初始化;
否则调用`setPressed()`来设置当前的按压状态->因为:
事件分发到这里就以及确定了哪个`View`消费了事件,这个时候就要把这个消息往回传,告诉高等级那些大人物们.
回传的起点自然就是当前所在的`view`的`onTouchEvent`->`dispatchSetPressed(pressed)`,
让整个view都知道事件消费情况,并且返回`result`告诉viewGroup
*/
//ViewGroup#dispatchTransformedTouchEvent()
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
/**
而ViewGroup#dispatchTouchEvent()收到返回值是这样的
*/
//ViewGroup#dispatchTouchEvent()
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
/**
这是一个循环的一部分,这个循环遍历child(子View),用`dispatchTransformedTouchEvent()`进行分发;
如果分发未果,没有消费事件,就遍历下一个子View;
如果分发成功执行其内部操作,可以看到,已经分发成功,并获取到目标`View`.
*/
case MotionEvent.ACTION_DOWN:
...
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
...
} else {
// Not inside a scrolling container, so show the feedback right away
/**
* Sets the pressed state for this view and provides a touch coordinate for
* animation hinting.
*
* @param pressed Pass true to set the View's internal state to "pressed",
* or false to reverts the View's internal state from a
* previously set "pressed" state.
* @param x The x coordinate of the touch that caused the press
* @param y The y coordinate of the touch that caused the press
*/
setPressed(true, x, y);
checkForLongClick(0);
}
break;
/**
撤销动作,算作没有消费事件
*/
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
...
break;
/**
移动
*/
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
事件分发-恶魔法则11条
1.同一序列事件是指从手指接触屏幕开始,到手指离开屏幕结束,中间产生的一系列以
ACTION_DOWN开始,中间N个ACTION_MOVE,最后以ACTION_UP结束的一系列事件.2.某个View一旦决定拦截事件(拦截了
ACTION_DOWN),那么剩下的ACTION_MOVE & ACTION_UP都只能交由它处理,并且它的onInterceptTouchEvent()不再会被调用(其实如果是子View的话,decor View的也不会再被调用).3.一个事件只能被一个View拦截消耗,因为后续不会再有拦截,也就没机会交给其它View处理.
4.某个View一旦开始处理事件,如果它不消耗
ACTION_DOWN事件(onTouchEvent返回false)那么同一事件序列中的其它事件都不会再交由它处理, 会交由他们的父元素处理(Activity),既父元素的onTouchEvent会被调用.5.如果View不消耗
ACTION_DOWN以外的事件,这个点击事件会消失,父元素的onTouchEvent不会被调用,最后这些消失的点击事件会传给Activity处理.6.ViewaGroup默认不拦截任何事件.
7.View没有
onInterceptTouchEvent,事件传递给它后它的onTouchEvent就会被调用.8.View的
onTouchEvent默认消耗事件,除非它的CLICKABLE和LONG_CLICKABLE都为false.9.View的
ENABLE不影响事件的消费,只有CLICKABLE影响.10.
onClick会发生的前提是View是可点击的,并且收到了一系列事件(ACTION_DOWN->ACTION_UP),并且设置了监听(listener).11.事件总是先传给父元素再分发给子View,可以通过
requestDissallowInterceptTouchEvent来干预父元素除了ACTION_DOWN事件的分发.\
