应用层事件分发
事件处理(View)
基于 Android29,View 的事件处理入口 dispatchTouchEvent
boolean dispatchTouchEvent(MotionEvent event)
传递 ACTION_DOWN 事件给 View 或该 View 是 target 了;true 表示消费事件,false 为消费
// View#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) { // 校验window或事件是否OBSCURED
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { // View是否enabled
result = true;
}
// View可用,设置了OnTouchListener
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // onTouch返回了返回,就消费了事件
result = true;
}
if (!result && onTouchEvent(event)) { // result返回true,就不会调用onTouchEvent了
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
- 事件先交给 accessibility
event.isTargetAccessibilityFocus()处理 - View 要 Enable
onTouchEvent(MotionEvent event) 处理单击,长按
// View#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) { // View 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了,但设置了click和longclick监听,依然会consume消费事件,只是不会响onClick或onLongClick监听而已
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) { // 不可点击,移除tap、长按监听
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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 && !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(); // 点击声音,回调
}
}
}
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();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
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);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); // 检测longPress事件,在500ms内没有click事件,那么执行onLongClick
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
final float ambiguousMultiplier =
ViewConfiguration.getAmbiguousGestureMultiplier();
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}
// Be lenient about moving outside of buttons
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;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback(); // MOVE事件移除longPress的callback
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
View 事件处理总结
OnTouchListener 和 OnClickListener
mOnTouchListener.onTouch(this, event) 该 View 的 onTouch() 返回值
返回 true,设置了 OnTouchListener 监听,onTouch() 返回 true,View 的 onTouchEvent() 不会执行
返回 false,没有设置 OnTouchListener 监听或者 onTouch() 返回 false,执行 onTouchEvent() 方法
注: 如果要屏蔽 onTouchEvent() 事件,直接在 onTouch() 中返回 true 就行了
View disable 是否可消费事件?
一个 View 设置为了 disable,但是其可 clickable 或 longclickable,那么也会消费该事件,只是不会调用 onClick() 和 onLongClick() 方法。
单击事件和长按事件区别
- 单击:按下 (down),然后在 500ms 内抬起来 (up)
- 长按:按下 (down,超过 500ms),不需要 up 起来
- 单击事件,按下和抬起时间间隔小于 500ms,属于单击事件;按下超过 500ms,那么会响应长按事件,再响应单击事件
View 的长按事件,都是在 DOWN 事件中,通过 postDelayed(),默认延迟 500 毫秒,如果超过 500ms 没有 UP 事件或这期间没有 MOVE 事件到来,那么认为是长按事件,调用 onLongClick() 方法;超过了 500ms,认为是单击事件,调用 onClick() 方法。
事件分发(ViewGroup)
分发流程
Activity#dispatchTouchEvent →
PhoneWindow#superDispatchTouchEvent →
DecorView(FrameLayout)#superDispatchTouchEvent →
ViewGroup#dispatchTouchEvent
小结
- 每次 ACTION_DOWN 事件到来,代表了一套新的事件,会清除之前的 touch target
- 事件拦截只在 ACTION_DOWN 才能拦截
- 只 childvisible 或 animation!=null,才有机会决定自己是否处理事件
- 如果 Child DOWN 事件没有拦截,但是在 MOVE 或者 UP 的时候进行了拦截;那么在第一次会将事件设置为 CANCEL 事件,并清空 mMotionTarget,直接返回 true;
事件分发的入口 ViewGroup#dispatchTouchEvent,从这里开始分析
// ViewGroup#dispatchTouchEvent 基于Android29
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// ...
boolean handled = false; //
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// ACTION_DOWN事件开始,清除所有的touch target(mFirstTouchTarget)
// 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();
}
// Check for interception.
final boolean intercepted; // 是否拦截,true表示拦截,false表示不拦截
// 首次ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 看看子View有没有通过requestDisallowInterceptTouchEvent请求父容易不要拦截事件,true表示子View期望父容易不要拦截事件,false表示父容器能拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// disallowIntercept=false,看父容器onInterceptTouchEvent返回,true拦截,false不拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 不是ACTION_DOWN事件或mFirstTouchTarget不为null,父容器拦截事件
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 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;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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 there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
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);
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();
}
// 只有down事件进来,这个处理意味着多指事件下只有一个child能响应down事件,那不管你其他指头落在哪里,都默认是这个view处理(已经有一个view消费了,再触摸到其他的未消费事件的view上执行)
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;
}
}
}
// 首次ACTION_DOWN事件到来,mFirstTouchTarget为null
// 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;
}
}
// 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;
}
一套事件有一个 ACTION_DOWN 事件,0 或多个 ACTION_MOVE 事件,1 个 ACTION_UP 事件
onInterceptTouchEvent 总结
你说说 viewgroup 的 onInterceptTouchEvent 执行情况,有子 view 消费和无子 view 消费的情况
public boolean dispatchTouchEvent(MotionEvent ev) {
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) { // Down事件或者mFirstTouchTarget不为空(有子view消费事件)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { // 子类未设置FLAG_DISALLOW_INTERCEPT标记,父容器是否拦截事件取决onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev); // onInterceptTouchEvent返回false不拦截,返回true拦截
ev.setAction(action); // restore action in case it was changed
} else { // 子类设置了FLAG_DISALLOW_INTERCEPT标记通知父容器不拦截事件,那么父容器不拦截事件
intercepted = false;
}
} else { // 不是Down事件且mFirstTouchTarget为null,默认拦截(即未找到子view消费事件,那么MOVE/UP事件父容器默认拦截)
intercepted = true;
}
TouchTarget newTouchTarget = null;
if (!canceled && !intercepted) { // 事件未取消且事件未被拦截
if (actionMasked == MotionEvent.ACTION_DOWN) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final boolean handled;
handled = child.dispatchTouchEvent(event); // 倒序遍历子view分发事件
if(handled) {
newTouchTarget = addTouchTarget(child, idBitsToAssign); // mFirstTouchTarget在这赋值,找到可消费事件的子View
break;
}
}
}
}
if (mFirstTouchTarget == null) { // 未找到可消费事件的子view,调用view#dispatchTouchEvent,最后调用onTouchEvent自己处理事件
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else { // 找到了可消费事件的子view
TouchTarget target = mFirstTouchTarget;
while (target != null) { // 遍历子mFirstTouchTarget
final TouchTarget next = target.next;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { // 分发事件给mFirstTouchTarget
handled = true;
}
target = next;
}
}
}
onInterceptTouchEvent 返回 false
- onInterceptTouchEvent 返回 false,即父容器不不拦截事件,此时 Down 事件会遍历子控件看是否消费事件,但子 View 未找到消费的事件;这个时候会调用父控件自身的 onTouchEvent 处理事件(down 事件父控件的 onInterceptTouchEvent 和子控件都会走;后续的 move/up 事件,父控件的 onInterceptTouchEvent 和所有子 view 都不会接收该事件了)
- onInterceptTouchEvent 返回 false,即父容器不拦截事件,此时 Down 事件会遍历子控件看是否消费事件,找到了一个子 View 能消费事件;这个时候 mFirstTouchTarget 就不为 null 有值了,就会将后续 move/up 交给 mFirstTouchTarget 消费(down 事件父控件的 onInterceptTouchEvent 和子控件都会走;后续的 move/up 事件,父控件的 onInterceptTouchEvent 和消费事件的子控件会走)
onInterceptTouchEvent 返回 true
- onInterceptTouchEvent 返回 true,即父容器拦截该事件,此时 Down 事件不会经过子控件(也就是子控件收不到任何 down/move/up 事件),会调用父控件自身的 onTouchEvent 事件处理(down 事件父控件的 onInterceptTouchEvent 会走,子控件不走;后续 move/up 事件父控件的 onInterceptTouchEvent 不会走)
ACTION_DOWN 事件
- 首先看是否拦截事件,intercepted 为 true 表拦截,false 不拦截
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;
}
- 如果 intercepted=true,拦截了事件,ViewGroup 自己处理事件,走
dispatchTransformedTouchEvent()
if (mFirstTouchTarget == null) {
// 没有touch targets所以当成普通的view处理
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); // canceled此时为false,child为null
} else {
/// ...
}
接着走 dispatchTransformedTouchEvent():
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
// child为null,调用View#dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
调用 View#dispatchTouchEvent 处理事件
- 如果 intercepted=false,不拦截事件,寻找 touch target(mFirstTouchTarget)
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (actionMasked == MotionEvent.ACTION_DOWN) { // 为ACTION_DOWN事件
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) { // newTouchTarget为null且childrenCount不为0(有孩子)
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) { // 拿最后一个孩子
if (!child.canReceivePointerEvents() && !isTransformedTouchPointInView(x, y, child, null)) { // 判断child是否VISIBLE||mCurrentAnimation!=null 叽叽判断触摸点是否在childView上,不在的话continue,不分发事件
continue;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 调用子View的dispatchTouchEvent处理事件
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign); // 首次ACTION_DOWN事件,target.next=null,mFirstTouchTarget = 当前子View的target
alreadyDispatchedToNewTouchTarget = true; // 为true
break; // 找到一个可以消费事件的子View,结束事件分发了
}
}
if (newTouchTarget == null && mFirstTouchTarget != null) { // 首次ACTION_DOWN事件,找到了newTouchTarget不为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;
}
}
}
// View#pointInView,判断触摸点是否在View上
public boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}
// 获取一个TouchTarget
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
看看找到了一个 touch target(看 mFirstTouchTarget!=null 了)
if (mFirstTouchTarget == null) { // 不拦截事件,未找到一个子View,
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else { // 不拦截事件,找到了一个子View(touch target),现在走这里 ,首次ACTION_DOWN事件,走到这里,已经事件被消费了,在这里什么也没做
// 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; // 首次mFirstTouchTarget=null
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // 首次ACTION_DOWN事件,如果找到了touch target,那么alreadyDispatchedToNewTouchTarget=true
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;
}
}
到这里,ACTION_DOWN 事件的拦截和非拦截都分析完毕
- 拦截,调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
- 不拦截,未找到一个 child 能消费事件(mFirstTouchTarget=null),调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
- 不拦截,找到一个 child 能消费事件(mFirstTouchTarget!=null),child#dispatchTouchEvent 处理事件(此处由指定的 child 处理事件)
ACTION_MOVE 事件
首次 MOVE_ 事件
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) { // 现在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;
}
看看父容器是否拦截事件
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// MOVE事件这里什么也没做
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) { // MOVE事件,之前未找到child touch target,那么还是调用父容器View.dispatchTouchEvent自己处理事件
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else { // MOVE事件,之前有找到child touch target
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // MOVE事件alreadyDispatchedToNewTouchTarget=false
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted; // MOVE事件,如果DOWN事件找到了要分发的子View,现在父容器要拦截掉该事件,那么需要取消分发Cancel事件给子View
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) { // 之前将事件分发给自己找到的child touch target
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next; // mFirstTouchTarget只为null了
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 此时为MOVE事件,acncel为true,child不为null
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) { // 如果事件被取消了,分发cancel给子view,这次MOVE事件到此为止
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// ...
}
到这里,首次 ACTION_MOVE 事件的拦截和非拦截都分析完毕
- 拦截,之前未找到一个 child 能消费事件,调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
- 拦截,之前找到一个 child 能消费事件,给 child 分发个 cancel 事件
- 不拦截,之前未找到一个 child 能消费事件(mFirstTouchTarget=null),调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
- 不拦截,之前找到一个 child 能消费事件(mFirstTouchTarget!=null),child#dispatchTouchEvent 处理事件(此处由指定的 child 处理事件)
第 2+ 次 MOVE 时间
无 touch target
// 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);
}
无 touch target,直接调用父 View 自己的 View#dispatchTouchEvent 处理时间
有 touch target,拦截
有 touch target,在 MOVE 时被拦截了,那么 mFirstTouchTarge 为 null,同无 touch target 了
有 touch target,不拦截
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;
}
有 touch target,不拦截,子 View 调用自己的 dispatchTouchEvent
ACTION_UP 事件
情况状态,清除 mFirstTouchTarget
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
}
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
事件冲突解决
内部拦截法解决 requestDisallowInterceptTouchEvent
- 如果父容器在 down 事件就给 intecept 了,那么子 view 收不到任何事件,子 view 的 dispatchTouchEvent 也不会调用,也就用不了内部解决冲突问题了
- 如果父容器 down 事件不 intecept,子 view 可以收到 down 事件并消费,但在 move 事件时给拦截了,可以内部调用
View#requestDisallowIntecept(disallow)解决事件冲突, 设置 true 递归父容器不拦截,false 拦截 - 要在 down 调用 requestDisallowInterceptTouchEvent(true),事件冲突一般都是在 move 来解决;在 up 时恢复 requestDisallowInterceptTouchEvent(false)
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
float startX = 0F;
float startY = 0F;
// 内部拦截法:子view处理事件冲突
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float deltX = ev.getX() - startX;
float deltY = ev.getY() - startY;
if (Math.abs(deltX) > Math.abs(deltY)) {
requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
requestDisallowInterceptTouchEvent(false);
break;
default:
break;
}
boolean b = super.dispatchTouchEvent(ev);
return b;
}
}
外部拦截法解决 onInterceptTouchEvent
外部拦截法:父容器处理冲突
父容器 onInterceptTouchEvent,我想要把事件分发给谁就分发给谁
public class BadViewPager extends ViewPager {
private int mLastX, mLastY;
public BadViewPager(@NonNull Context context) {
super(context);
}
public BadViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
// 外部拦截法:父容器处理冲突
// 我想要把事件分发给谁就分发给谁
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// if (event.getAction() == MotionEvent.ACTION_DOWN){
// super.onInterceptTouchEvent(event);
// return false;
// }
// return true;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastX = (int) event.getX();
mLastY = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.onInterceptTouchEvent(event);
}
}