目标
1. 事件分发流程;
2. onClick执行时机;
关于事件分发, 先只记住流程也可以, 事件分发是从ViewGroup开始执行, 因为View没有子类, 也就没有所谓的事件分发. 或者结合Activity来进行分析, Activity顶层View是DecorView, 而DecorView instanceof ViewGroup, 所以事件分发是从ViewGroup开始.
打算从以下几个方面进行分析:
1、ViewGroup.dispatchTouchEvent与View.dispatchTouchEvent
2、ViewGroup.requestDisallowInterceptTouchEvent
3、ViewGroup.onInterceptTouchEvent
4、View.onTouch
5、View.onTouchEvent
6、View.onLongClick
7、View.onClick
8、ViewGroup.mFirstTouchTarget
9、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP之间的关系
在分析之前, 先写几个结论:
1、mFirstTouchTarget作为是否存在ChildView消费了事件的一个标记, 如果成功消费, 将当前消费MotionEvent的View指向该mFirstTouchTarget, 即mFirstTouchTarget != null, 如果没有View或者存在View但是该View没有消费事件, 则mFirstTouchTarget == null;
2、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTOIN_UP三个事件默认情况下, 只有上一个事件执行时onTouchEvent返回true时, 下一个事件才会继续执行.
事件分发的流程可能比较绕, 所以分析完成之后一定要画一个流程图, 切忌死记硬背, 仔细看完之后发现其实可以理解着去记忆事件分发的过程的.
一、事件分发的起始点ViewGroup.dispatchTouchEvent
1.1 ViewGroup.dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
// 关于第二个变量mFirstTouchTarget需要结合下文dispatchTransformedTouchEvent才能明白.
// 1. 触摸事件发生时actionMasked默认为MotionEvent.ACTION_DOWN, 所以此时会直接进入到
// if语句内部, 对intercepted进行赋值.
// 2. 如果在View.onTouchEvent中我们令ACTION_DOWN时返回false, 那么下文就不会触发
// addTouchTarget对mFirstTouchTarget进行赋值, 也就是mFirstTouchTarget = null,
// 所以当actionMasked == MotionEvent.ACTION_UP时, 由于mFirstTouchTarget = null,
// 会进入到else中, 然后将intercepted设置为true, 然后直接跳过下文的事件分发操作.
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 首先会获取disallowIntercept, 该值默认为false: 也就是说默认允许进行事件拦截操作,
// 该变量通过requestDisallowInterceptTouchEvent进行设置.
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// onInterceptTouchEvent默认返回false, 可以重写该方法然后根据MotionEvent具体
// 的取值进行返回值的控制.
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
// disallowIntercept为true时, 设置intercepted值为false
intercepted = false;
}
} else {
intercepted = true;
}
// 小结1:
// 1. disallowIntercept默认为false, 也就是说允许进行事件拦截操作, 此时再次获取
// onInterceptTouchEvent的值.
// 2. 而disallowIntercept=true时也就是不允许拦截时, 则直接将intercepted置为false.
// 3. 通过对下文的分析可知, intercepted = false表示进行事件分发操作.
// 4. 因此可以得出结论, 当允许父View进行事件拦截时, 也就是disallowIntercept=false时,
// 通过onInterceptTouchEvent(ev)判断应该如何进行拦截.
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 通过这里可以看出intercepted的值的作用, true时跳过if语句不进行事件分发,
// false时进入if进行事件分发
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
// 能进行事件分发的前提条件是当前ViewGroup有ChildView, 如果没有ChildView, 事件分发
// 也就不存在, 当前View自己消费该MotionEvent.
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 preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
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);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
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;
}
resetCancelNextUpFlag(child);
// 这里进行事件分发操作, 处理childView自己的事件, 返回值这里的用途是?
// 返回true表示View自己消费了事件, 如果返回false, 则表示View当前不会消费事件.
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();
// 1. addTouchTarget中会对mFirstTouchTarget进行赋值, 能进入到这里的前提是:
// 存在事件分发&&dispatchTransformedTouchEvent返回true.
// 2. mFirstTouchTarget指向的是当前获取MotionEvent的View对应的Target.
// 3. 现在考虑一个问题dispatchTransformedTouchEvent什么情况会返回true?
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// mFirstTouchTarget = null有以下几种可能:
// 1. 根据触摸点没有找到合适的ChildView;
// 2. 找到了合适的ChildView但是该ChildView没有消费该事件;
// 然后ViewGroup自己消费掉该触摸事件.
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
1.2 ViewGroup.dispatchTransformedTouchEvent
通过MotionEvent找到合适的ChildView, 然后将MotionEvent交给ChildView, ChildView自己去消费该事件, 如果没找到合适的ChildView, 则当前View自己消费该事件
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
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;
}
// 如果child为空, 则当前ViewGroup自己消费该MotionEvent, 以前看代码时有一个困惑点, 如果
// ViewGroup没有ChildView, 它是如何自己消费MotionEvent的? 当时并没有找到答案.
// 终于发现是在这里, ViewGroup继承自View, 所以super.dispatchTouchEvent指向的是
// View.dispatchTouchEvent, 那么事件消费肯定是在View.dispatchTouchEvent中进行的.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// 如果ChildView不为空, 则将MotionEvent交给ChildView自己去处理
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
小结:
1、当进行事件分发时, 如果是非MotionEvent.ACTION_CANCEL模式, 首先判断根据触摸点能否获取到ChildView, 如果能获取到ChildView, 则将该MotionEvent交给ChildView自己去处理, 如果没有找到合适的ChildView, 则ViewGroup自己处理该MotionEvent.
2、ViewGroup如何进行事件消费? 由于super指向View, 所以super.dispatchTouchEvent会触发View.dispatchTouchEvent, 因此事件消费必然在View.dispatchTouchEvent中执行. 这里也就是ViewGroup自己消费MotionEvent的入口点
二、View进行事件消费
2.1 View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
final int actionMasked = event.getActionMasked();
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;
}
小结:
在第一部分ViewGroup.dispatchTouchEvent中, mFirstTouchTarget取值取决于ViewGroup.dispatchTransformedTouchEvent的返回值, 而dispatchTransformedTouchEvent内部优势调用了View.dispatchTouchEvent并获取返回值, 所以这里总结dispatchTouchEvent返回true/false的情况;
结合图和代码, View.dispatchTouchEvent的返回值取决于onTouch与onTouchEvent的返回值, 而onTouchEvent能否执行, 是取决于onTouch的, 这里先假设onTouch为false的情况下, 对onTouchEvent进行分析.
2.2 View.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == 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.
// 控件DISABLED的情况下, 如果控件具备Click能力, 仍然认为控件能自己消费事件.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
// 如果控件不具备Click事件, 直接返回false, 也就是表示View不会消费触摸事件
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// 所以onClick的执行取决于mHasPerformedLongPress的值, 而mHasPerformedLongPress
// 的值取决于onLongClick的返回值, 所以当onLongClick返回false时onClick才会执行.
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// 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();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
// 如果onLongClick返回true, mHasPerformedLongPress = true;
// 如果onLongClick返回false, mHasPerformedLongPress = false;
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
// 这里可以看出, 默认情况下, 只要View具备Click或者LongClick能力, 则进入if语句返回true.
return true;
}
// 返回如果不具备click或者longclick能力, 则返回false,
return false;
}
小结:
1、默认情况下如果View具备Click或者LongClick能力, 则View.onTouchEvent返回true, 同时MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP会依次执行;
2、如果View不具备Click或者LongClick能力, 则View.onTouchEvent返回false;
3、思考一个问题哈, 如果根据MotionEvent不同阶段取值不同, 返回true/false会对事件分发有什么影响吗?