Android 事件分发
Android 事件分发
本篇文章主要简单介绍下Android中的事件分发,和大家一起学习,进步,有问题也希望大家及时指证修改.
1: onClick和OnTouch
首先我们在单独的activity中添加个按钮button.增加点击事件setOnClickListener:
button.setOnClickListener(v -> Log.i(TAG, "onClick: "));
接着添加OnTouch:
button.setOnTouchListener((v, event) -> {Log.i(TAG, "onTouch: " + event.getAction());return false;
});
我们执行下点击事件.可以看到输出如下:
2024-04-09 20:54:11.219 17770-17770/? I/testxxx: onTouch: 0
2024-04-09 20:54:11.234 17770-17770/? I/testxxx: onTouch: 2
2024-04-09 20:54:11.294 17770-17770/? I/testxxx: onTouch: 2
2024-04-09 20:54:11.299 17770-17770/? I/testxxx: onTouch: 2
2024-04-09 20:54:11.300 17770-17770/? I/testxxx: onTouch: 1
2024-04-09 20:54:11.306 17770-17770/? I/testxxx: onClick:
OnTouch的事件比较多一些,包含了按下,移动,抬起事件.
事件执行的顺序我们可以看到是OnTouch->onClick.
我们在修改下OnTouch方法:
2024-04-10 10:03:53.245 19437-19437/? I/testxxx: onTouch: 0
2024-04-10 10:03:53.264 19437-19437/? I/testxxx: onTouch: 2
2024-04-10 10:03:53.298 19437-19437/? I/testxxx: onTouch: 2
2024-04-10 10:03:53.304 19437-19437/? I/testxxx: onTouch: 2
2024-04-10 10:03:53.305 19437-19437/? I/testxxx: onTouch: 1
可以看到这里已经没有onCLick事件的执行了.
原因是:onTouch返回true,就代表了该点击事件被onTouch消费掉了,不会继续向下执行. 具体的源码分析可以继续往下看.
2: onInterceptTouchEvent/dispatchTouchEvent/onTouchEvent
首先我们自定义下relationlayout以及button:
package com.test.touchtest;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;/*** @Author: zh* @Time: 24-4-10.* @Describe:*/
public class CustomLayout extends RelativeLayout {private static final String TAG = "CustomLayout";public CustomLayout(Context context) {super(context);}public CustomLayout(Context context, AttributeSet attrs) {super(context, attrs);}public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(TAG, "onInterceptTouchEvent: " + ev.getAction());return super.onInterceptTouchEvent(ev);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(TAG, "dispatchTouchEvent: " + ev.getAction());return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(TAG, "onTouchEvent: " + event.getAction());return super.onTouchEvent(event);}
}
package com.test.touchtest;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;/*** @Author: zh* @Time: 24-4-10.* @Describe:*/
public class CustomButton extends androidx.appcompat.widget.AppCompatButton {private static final String TAG = "CustomButton";public CustomButton(Context context) {super(context);}public CustomButton(Context context, AttributeSet attrs) {super(context, attrs);}public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(TAG, "dispatchTouchEvent: " + ev.getAction());return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(TAG, "onTouchEvent: " + event.getAction());return super.onTouchEvent(event);}
}
执行下button的点击.
2024-04-10 10:16:57.059 25026-25026/com.test.touchtest I/CustomLayout: dispatchTouchEvent: 0
2024-04-10 10:16:57.059 25026-25026/com.test.touchtest I/CustomLayout: onInterceptTouchEvent: 0
2024-04-10 10:16:57.060 25026-25026/com.test.touchtest I/CustomButton: dispatchTouchEvent: 0
2024-04-10 10:16:57.060 25026-25026/com.test.touchtest I/CustomButton: onTouchEvent: 0
2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomLayout: dispatchTouchEvent: 2
2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomLayout: onInterceptTouchEvent: 2
2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomButton: dispatchTouchEvent: 2
2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomButton: onTouchEvent: 2
2024-04-10 10:16:57.116 25026-25026/com.test.touchtest I/CustomLayout: dispatchTouchEvent: 1
2024-04-10 10:16:57.116 25026-25026/com.test.touchtest I/CustomLayout: onInterceptTouchEvent: 1
2024-04-10 10:16:57.117 25026-25026/com.test.touchtest I/CustomButton: dispatchTouchEvent: 1
2024-04-10 10:16:57.117 25026-25026/com.test.touchtest I/CustomButton: onTouchEvent: 1
2024-04-10 10:16:57.126 25026-25026/com.test.touchtest I/custom: onClick:
可以看到大概的执行顺序如下:
- ViewGroup: dispatchTouchEvent
- ViewGroup: onInterceptTouchEvent
- Button: dispatchTouchEvent
- Button: onTouchEvent
- Button: onClick
我们在增加下button的setOnToucnListener,打印输出如下:
2024-04-10 11:01:38.824 2160-2160/? I/CustomLayout: dispatchTouchEvent: 0
2024-04-10 11:01:38.825 2160-2160/? I/CustomLayout: onInterceptTouchEvent: 0
2024-04-10 11:01:38.825 2160-2160/? I/CustomButton: dispatchTouchEvent: 0
2024-04-10 11:01:38.826 2160-2160/? I/customActivity: onTouch: 0
2024-04-10 11:01:38.826 2160-2160/? I/CustomButton: onTouchEvent: 0
2024-04-10 11:01:38.843 2160-2160/? I/CustomLayout: dispatchTouchEvent: 2
2024-04-10 11:01:38.843 2160-2160/? I/CustomLayout: onInterceptTouchEvent: 2
2024-04-10 11:01:38.843 2160-2160/? I/CustomButton: dispatchTouchEvent: 2
2024-04-10 11:01:38.843 2160-2160/? I/customActivity: onTouch: 2
2024-04-10 11:01:38.843 2160-2160/? I/CustomButton: onTouchEvent: 2
2024-04-10 11:01:38.902 2160-2160/? I/CustomLayout: dispatchTouchEvent: 1
2024-04-10 11:01:38.902 2160-2160/? I/CustomLayout: onInterceptTouchEvent: 1
2024-04-10 11:01:38.902 2160-2160/? I/CustomButton: dispatchTouchEvent: 1
2024-04-10 11:01:38.902 2160-2160/? I/customActivity: onTouch: 1
2024-04-10 11:01:38.902 2160-2160/? I/CustomButton: onTouchEvent: 1
2024-04-10 11:01:38.908 2160-2160/? I/customActivity: onClick:
可以看到大概的执行顺序如下:
- ViewGroup: dispatchTouchEvent
- ViewGroup: onInterceptTouchEvent
- Button: dispatchTouchEvent
- Button: onTouch
- Button: onTouchEvent
- Button: onClick
3: 源码分析
我们简单看下源码:
-
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) {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;}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) {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 checkremoveLongPressCallback();// Only perform take click actions if we were in the pressed stateif (!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 nowmUnsetPressedState.run();}removeTapCallback();}mIgnoreNextUpEvent = false;break;case MotionEvent.ACTION_DOWN:if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {mPrivateFlags3 |= PFLAG3_FINGER_DOWN;}mHasPerformedLongPress = false;if (!clickable) {checkForLongClick(0, x, y);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 awaysetPressed(true, x, y);checkForLongClick(0, x, y);}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);}// Be lenient about moving outside of buttonsif (!pointInView(x, y, mTouchSlop)) {// Outside button// Remove any future long press/tap checksremoveTapCallback();removeLongPressCallback();if ((mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);}mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;}break;}return true;}return false; }
我们关注下MotionEvent.ACTION_UP事件,可以看到在事件处理的最后有这么一段代码:
if (mPerformClick == null) {mPerformClick = new PerformClick(); } if (!post(mPerformClick)) {performClickInternal(); }
-
performClickInternal() 源码如下:
private boolean performClickInternal() {// Must notify autofill manager before performing the click actions to avoid scenarios where// the app has a click listener that changes the state of views the autofill service might// be interested on.notifyAutofillManagerOnClick();return performClick(); }
-
performClick()源码:
public boolean performClick() {// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result; }
可以看到在li != null && li.mOnClickListener != null条件下,执行了:
li.mOnClickListener.onClick(this);
所以也可以解释了:
事件onTouchEvent()->onClick()的执行顺序.
然后我们看下View的dispathTouchEvent()的源码:
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 gesturestopNestedScroll();}if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//noinspection SimplifiableIfStatementListenerInfo 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;}}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;
}
可以看到源码中的这段代码:
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;
}
这里调用了mOnTouchListener.onTouch事件.
所以也就证明了之前事件执行的顺序:
view的dispatchTouchEvent()->mOnTouchListener.onTouch()->onTouchEent()->onClick().
并且如果li.mOnTouchListener.onTouch(this, event)返回true,也可以看到onTouchEvent(event)并不会执行.
也就解释了我们前面如果修改了onTouch返回值为true,并不会执行onClick事件的原因.
接着我们在看下ViewGroup的dispatchTouchEvent()源码:
public boolean dispatchTouchEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}// If the event targets the accessibility focused view and this is it, start// normal event dispatch. Maybe a descendant is what will handle the click.if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {ev.setTargetAccessibilityFocus(false);}boolean handled = false;if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// 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;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 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 downfinal 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 (!canViewReceivePointerEvents(child)|| !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 indexfor (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();}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;}}}// 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;
}
-
actionMasked == MotionEvent.ACTION_DOWN 条件触发时,将mFirstTouchTarget置为null.
-
disallowIntercept定义了是否禁用事件拦截.可以通过requestDisallowInterceptTouchEvent修改.默认情况下为false.
-
接着进入if条件,默认情况下,执行onInterceptTouchEvent.
public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false; }
-
在顺序执行可以看到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) {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; }
可以看到主要会执行child.dispatchTouchEvent()方法.
这里其实也就验证了我们先前的调用顺序.
ViewGroup的dispatchTouchEvent->ViewGroup的onInterceptTouchEvent->子view的dispatchTouchEvent.