在Android事件分發(fā)流程(API-27)(1)中我們有分析到當(dāng)我們在屏幕的一個點擊會走到Activity.dispatchTouchEvent()
啃匿,現(xiàn)在我們分析Actvitiy.dispatchTouchEvent()
之后的流程
-
Activity分發(fā)流程
-
Activity.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // onUserInteraction是Activity的一個空方法,當(dāng)有用戶和設(shè)備有交互就會觸發(fā) onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { // 調(diào)用window的superDispatchTouchEvent(), 這里的getWindow我們都知道是PhoneWindow return true; } return onTouchEvent(ev); }
這里我們看到當(dāng)
PhoneWindow
返回true
就會被攔截掉,dispatchTouchEvent
直接返回true
,而不再執(zhí)行Activity.onTouchEvent()
方法了 -
PhoneWindow.superDispatchTouchEvent()
@Override public boolean superDispatchTouchEvent(MotionEvent event) { // 這里的mDecor我們都知道是DecorView,所以PhoneWindow直接交給了DecorView處理了 return mDecor.superDispatchTouchEvent(event); }
-
DecorView.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) { // DecorView繼承的FrameLayout,F(xiàn)rameLayout繼承ViewGroup,而FrameLayout并沒有重寫dispatchTouchEvent()方法绿饵,所以直接調(diào)用ViewGroup的該方法 return super.dispatchTouchEvent(event); }
上面的流程我們可以知道,在
getWindow().superDispatchTouchEvent(ev)
的流程交給了ViewGroup
處理瓶颠,我們先來看看ViewGroup
返回false
之后的流程Activity.onTouchEvent()
-
Activity.onTouchEvent()
public boolean onTouchEvent(MotionEvent event) { // mWindow是PhoneWindow拟赊,PhoneWindow繼承Window這個抽象類,PhoneWindow沒有重寫該方法粹淋,直接看Window的該方法 if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
-
Window.shouldCloseOnTouch()
/** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { final boolean isOutside = event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) || event.getAction() == MotionEvent.ACTION_OUTSIDE; if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) { // 如果是在Window的區(qū)域之外吸祟,則返回true return true; } // 否則返回false return false; } // 這里主要是判斷是否在Window的邊界外 private boolean isOutOfBounds(Context context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = getDecorView(); return (x < -slop) || (y < -slop) || (x > (decorView.getWidth()+slop)) || (y > (decorView.getHeight()+slop)); }
所以
Activity.onTouchEvent()
主要是判斷是否在Window的區(qū)域之外瑟慈,如果在區(qū)域之外則finish
當(dāng)前Activity
,并返回true
屋匕,否則返回false
葛碧,總之到這里Activity
的dispatchTouchEvent
的事件分發(fā)流程就結(jié)束了 -
流程圖
-
-
ViewGroup分發(fā)流程
上面我們分析了
Activity
層的事件分發(fā)的簡單流程,現(xiàn)在來看下ViewGroup.dispatchTouchEvent
之后的流程是怎樣的-
ViewGroup.dispatchTouchEvent
里的流程較多过吻,我們只看主要的代碼@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { // 用于調(diào)試进泼,輸入事件一致性校驗,View中的InputEventConsistencyVerifier屬性 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; // 返回值纤虽,默認(rèn)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. // 當(dāng)前ViewGroup是否要攔截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // FLAG_DISALLOW_INTERCEPT 禁止攔截標(biāo)識乳绕,可以調(diào)用requestDisallowInterceptTouchEvent(boolean disallowIntercept)禁止父ViewGroup攔截 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 1. 重點來了,調(diào)用了ViewGroup.onInterceptTouchEvent 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. // 當(dāng)前事件是否被取消 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) { // 如果事件沒有被取消也沒有被攔截逼纸,則分發(fā)給對應(yīng)的子View/ViewGroup // 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(); // 遍歷所有的子View/ViewGroup 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); // 調(diào)用子控件進行分發(fā) dispatchTransformedTouchEvent 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. // 沒有子視圖接收洋措,分發(fā)給當(dāng)前視圖 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; }
-
將事件分發(fā)給指定的控件
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) { // 調(diào)用onTouchEvent方法處理 handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. // 計算觸摸事件id 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) { // 前后id相同,不需要重新計算 if (child == null || child.hasIdentityMatrix()) { if (child == null) { // super這里是View樊展,child為空,把自己當(dāng)成View堆生,調(diào)用dispatchTouchEvent方法 // 調(diào)用onTouchEvent方法處理 handled = super.dispatchTouchEvent(event); } else { // 不為空专缠,調(diào)用相應(yīng)子View/ViewGroup的dispatchTouchEvent 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; }
-
是否攔截方法,
ViewGroup.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())) { // true攔截 return true; } // false不攔截 return false; }
-
ViewGroup
的分發(fā)流程相對較為復(fù)雜淑仆,簡單流程圖如下
-
-
View分發(fā)流程
-
接下來分析
View.dispatchTouchEvent
/** * 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); } // 返回值涝婉,默認(rèn)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)) { // View是Enable狀態(tài)且處理ScrollBar的drag操作返回true result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // mOnTouchListener不為空(設(shè)置了setOnTouchListener) // 且View是Enable狀態(tài) // 且OnTouchListener.onTouch返回true result = true; } if (!result && onTouchEvent(event)) { // result為false則執(zhí)行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; }
當(dāng)
result
為false
時,會調(diào)用onTouchEvent
方法蔗怠,這里OnTouchListener.onTouch
方法會執(zhí)行在performClick
之前墩弯,即可能會攔截OnClickListener
事件,使得onClick
方法不會執(zhí)行寞射,所以當(dāng)我們重寫setOnTouchListener
方法時AS往往會給我們一個如下提示onTouch should call View#performClick when a click is detected less... (?F1) If a View that overrides onTouchEvent or uses an OnTouchListener does not also implement performClick and call it when clicks are detected, the View may not handle accessibility actions properly. Logic handling the click actions should ideally be placed in View#performClick as some accessibility services invoke performClick when a click action should occur.
-
View.onTouchEvent
public boolean onTouchEvent(MotionEvent event) { ... if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: // 抬起 ... 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 // 執(zhí)行點擊操作 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(); } // 通過Handler.post(Runnable)方式執(zhí)行performClick方法 if (!post(mPerformClick)) { // post方式失敗則直接調(diào)用performClick performClick(); } } } ... break; case MotionEvent.ACTION_DOWN: // 按下 ... break; case MotionEvent.ACTION_CANCEL: // 取消 ... break; case MotionEvent.ACTION_MOVE: // 滑動 ... break; } return true; } return false; }
-
View.performClick()
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { // mClickListener不為空渔工,即setOnClickListener() playSoundEffect(SoundEffectConstants.CLICK); // 執(zhí)行onClick方法 li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); // 默認(rèn)返回false return result; }
-
流程圖
-
-
整體流程