该系列文章分三篇:
- 一步步探索学习Android Touch事件分发传递机制(一)通过写demo打Log,以ACTION_DOWN事件为例,完整了解整个Android Touch事件分发传递机制。
- 一步步探索学习Android Touch事件分发传递机制(二)探索了ACTION_MOVE和ACTION_UP事件的分发传递规律。
- 一步步探索学习Android Touch事件分发传递机制(三)即本篇,将通过Android源码分析,从本质上认识Android Touch事件分发传递机制。
- /**
- * Called to process touch screen events. You can override this to
- * intercept all touch screen events before they are dispatched to the
- * window. Be sure to call this implementation for touch screen events
- * that should be handled normally.
- *
- * @param ev The touch screen event.
- *
- * @return boolean Return true if this event was consumed.
- */
- public
- boolean
- dispatchTouchEvent
- (MotionEvent ev)
- {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- //当判断是ACTION_DOWN事件,回调onUserInteraction()
- onUserInteraction();
- }
- if (getWindow().superDispatchTouchEvent(ev)) {
- //getWindow()方法拿到的是PhoneWindow的实例
- return true;
- }
- //如果没有找到消费该事件的子View,最终会交给Activity的onTouchEvent()处理
- return onTouchEvent(ev);
- }
- @Override
- public
- boolean
- superDispatchTouchEvent
- (MotionEvent event)
- {
- return mDecor.superDispatchTouchEvent(event);
- }
- @Override
- 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 accessiiblity 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 (!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 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();
- }
- 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;
- }
- // 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;
- }
这里面涉及到一个方法:***requestDisallowInterceptTouchEvent()***方法。这个方法确定mGroupFlags的取值,控制请求父布局不拦截该事件,而是交给自己去做处理。这个方法在处理滑动冲突等场景时经常用到。但在这里为了整个源码分析的逻辑简洁清晰,不再具体分析该方法的代码。
- ...
- 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.
- }
- ...
- /**
- * Pass the touch screen motion event down to the target view, or this
- * view if it is the target.
- *
- * @param event The motion event to be dispatched.
- * @return True if the event was handled by the view, false otherwise.
- */
- 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)) {
- if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
- result = true;
- }
- //noinspection SimplifiableIfStatement
- 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;
- }
- }
- 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;
- }
注意:值得注意的是,ViewGroup本身并没有重写View的onTouchEvnet()方法,所以这里如果回传,也是调用的父类View.java的onTouchEvent()方法。
- /**
- * Called when a touch screen event was not handled by any of the views
- * under it. This is most useful to process touch events that happen
- * outside of your window bounds, where there is no view to receive it.
- *
- * @param event The touch screen event being processed.
- *
- * @return Return true if you have consumed the event, false if you haven't.
- * The default implementation always returns false.
- */
- public
- boolean
- onTouchEvent
- (MotionEvent event)
- {
- if (mWindow.shouldCloseOnTouch(this, event)) {
- finish();
- return true;
- }
- return false;
- }
前面说了,ViewGroup没有重写View的onTouchEvent()方法,所以继承VIewGroup时,调用的还是View的onTouchEvent()。
- /**
- * Implement this method to handle touch screen motion events.
- * <p>
- * If this method is used to detect click actions, it is recommended that
- * the actions be performed by implementing and calling
- * {@link #performClick()}. This will ensure consistent system behavior,
- * including:
- * <ul>
- * <li>obeying click sound preferences
- * <li>dispatching OnClickListener calls
- * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
- * accessibility features are enabled
- * </ul>
- *
- * @param event The motion event.
- * @return True if the event was handled, false otherwise.
- */
- 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 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)) {
- 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();
- }
- 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 away
- setPressed(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 buttons
- if (!pointInView(x, y, mTouchSlop)) {
- // Outside button
- // Remove any future long press/tap checks
- removeTapCallback();
- removeLongPressCallback();
- if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
- setPressed(false);
- }
- mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
- }
- break;
- }
- return true;
- }
- return false;
- }
注意:注意到一段比较重要的代码这段代码在判断是up事件之后调用了performClick()方法,这个方法回去回调onClickListener接口里面的onClick()方法。 这结合前面dispatchTouchEvent()方法中ACTION_DOWN事件会去调用onTouch,可见onTouch比onClick优先。
- if (!post(mPerformClick)) { performClick(); }
- 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;
- }
来源: https://juejin.im/post/5a2528c3518825320325c8a4