当前位置: 首页 > news >正文

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:

可以看到大概的执行顺序如下:

  1. ViewGroup: dispatchTouchEvent
  2. ViewGroup: onInterceptTouchEvent
  3. Button: dispatchTouchEvent
  4. Button: onTouchEvent
  5. 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:

可以看到大概的执行顺序如下:

  1. ViewGroup: dispatchTouchEvent
  2. ViewGroup: onInterceptTouchEvent
  3. Button: dispatchTouchEvent
  4. Button: onTouch
  5. Button: onTouchEvent
  6. Button: onClick

3: 源码分析

我们简单看下源码:

  1. 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();
    }
    
  2. 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();
    }
    
  3. 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;
}
  1. actionMasked == MotionEvent.ACTION_DOWN 条件触发时,将mFirstTouchTarget置为null.

  2. disallowIntercept定义了是否禁用事件拦截.可以通过requestDisallowInterceptTouchEvent修改.默认情况下为false.

  3. 接着进入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;
    }
    
  4. 在顺序执行可以看到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.

相关文章:

  • 美团一面4/9
  • 记Kubernetes(k8s):访问 Prometheus UI界面:Warning: Error fetching server time
  • 10. TypeScript面向对象的类(Class)
  • vue2转vue3一些属性使用方法总结 (持续更新中)
  • MySql并发事务问题
  • winform datagridView 一次删除20000条数据
  • SpringBoot快速入门笔记(5)
  • GPT提示词分享 —— 中医
  • mysql中表的设计
  • 帝国CMS模板源码整站安装说明(图文)
  • APIFY集成客服系统:提升用户运营效率
  • 技术 SEO 初学者指南
  • hadoop:案例:将顾客在京东、淘宝、多点三家平台的消费金额汇总,然后先按京东消费额排序,再按淘宝消费额排序
  • 详解 Redis 在 Centos 系统上的安装
  • STM32为什么不能跑Linux?
  • [ JavaScript ] 数据结构与算法 —— 链表
  • [译] 怎样写一个基础的编译器
  • CSS 提示工具(Tooltip)
  • hadoop集群管理系统搭建规划说明
  • Magento 1.x 中文订单打印乱码
  • MySQL的数据类型
  • PHP面试之三:MySQL数据库
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • Redis学习笔记 - pipline(流水线、管道)
  • sessionStorage和localStorage
  • Spark学习笔记之相关记录
  • Sublime Text 2/3 绑定Eclipse快捷键
  • Vue 动态创建 component
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 如何解决微信端直接跳WAP端
  • 新手搭建网站的主要流程
  • 怎么将电脑中的声音录制成WAV格式
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • 《码出高效》学习笔记与书中错误记录
  • Linux权限管理(week1_day5)--技术流ken
  • puppet连载22:define用法
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (6)添加vue-cookie
  • (Git) gitignore基础使用
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (理论篇)httpmoudle和httphandler一览
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (五)c52学习之旅-静态数码管
  • (转)C语言家族扩展收藏 (转)C语言家族扩展