View.hasIdentityMatrix():Returns true if the transform matrix is the identity matrix. Recomputes the matrix if necessary.
View.getInverseMatrix():Utility method to retrieve the inverse of the current mMatrix property. We cache the matrix to avoid recalculating it when transform properties have not changed.
resetCancelNextUpFlag(child); // dispatchTransformedTouchEvent 将 motion event 转换为特定子 view 的坐标空间,过滤掉不相关的 pointer ID,并在必要时覆盖其 action。 // 如果 child 为 null,则假定 MotionEvent 将改为发送到此 ViewGroup。 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 如果子 view 消费了 DOWN 事件,则继续 // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (intj=0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // addTouchTarget():创建一个新的 TouchTarget,其 next 赋值为 mFirstTouchTarget,然后把这个新的 TouchTarget 赋值给 mFirstTouchTarget 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; } } // ------------------------处理 DOWN 事件结束 或 POINTER_DOWN,HOVER_MOVE 事件----------------------- }
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // 没有子 view 消费 DOWN 事件时,mFirstTouchTarget == null // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // DOWN 事件被消费 // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTargetpredecessor=null; TouchTargettarget= mFirstTouchTarget; while (target != null) { finalTouchTargetnext= target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // DOWN 事件 handled = true; } else { // 其他事件 finalbooleancancelChild= resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { // 分发事件给子 view 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(); // mFirstTouchTarget 置空;清除 PFLAG_CANCEL_NEXT_UP_EVENT;清除 FLAG_DISALLOW_INTERCEPT } elseif (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { finalintactionIndex= ev.getActionIndex(); finalintidBitsToRemove=1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } }
// 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. finalintoldAction= 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. finalintoldPointerIdBits= event.getPointerIdBits(); finalintnewPointerIdBits= 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) { returnfalse; }
// 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()) { // hasIdentityMatrix() 为 true 时,代表子 view 没有经过 translation, scale 等转换 if (child == null) { handled = super.dispatchTouchEvent(event); } else { finalfloatoffsetX= mScrollX - child.mLeft; finalfloatoffsetY= mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY);
publicbooleandispatchTouchEvent(MotionEvent event) { booleanresult=false; finalintactionMasked= event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // DOWN 事件时,停止嵌套滚动 stopNestedScroll(); }
if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { // 处理鼠标拖动 ScrollBar result = true; } ListenerInfoli= mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // 1. 先调用 OnTouchListener.onTouch() 处理事件 result = true; }
if (!result && onTouchEvent(event)) { 2. 再调用 onTouchEvent() 处理事件 result = true; } }
// 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(); // UP 或 CANCEL 或 (DOWN 未被消费) 时,停止嵌套滚动 }
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: // -------------------UP 事件------------------ mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } booleanprepressed= (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. booleanfocusTaken=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 = newPerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } } }
if (mUnsetPressedState == null) { mUnsetPressedState = newUnsetPressedState(); }
if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } elseif (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); }
case MotionEvent.ACTION_DOWN: // -------------------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. booleanisInScrollingContainer= 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 = newCheckForTap(); } 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); } break;
case MotionEvent.ACTION_MOVE: // -------------------MOVE 事件------------------ if (clickable) { drawableHotspotChanged(x, y); }
finalintmotionClassification= event.getClassification(); finalbooleanambiguousGesture= motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; inttouchSlop= mTouchSlop; if (ambiguousGesture && hasPendingLongPressCallback()) { 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(); longdelay= (long) (ViewConfiguration.getLongPressTimeout() * mAmbiguousGestureMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); checkForLongClick( delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= mAmbiguousGestureMultiplier; }
// 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; }
finalbooleandeepPress= motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; if (deepPress && hasPendingLongPressCallback()) { // process the long click action immediately removeLongPressCallback(); checkForLongClick( 0/* send immediately */, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); }