Android MotionEvent傳遞流程 源碼筆記

  1. ViewGroup的boolean dispatchTouchEvent(MotionEvent ev):

    • mInputEventConsistencyVerifier是處於debug目的使用,可以忽略。
    • onFilterTouchEventForSecurity(MotionEvent event)來檢測這個MotionEvent是否應該被dispatch. 只有通過了,才能繼續向下走.
    • 使用action & MotionEvent.ACTION_MASK來獲取該MotionEvent的Action,使用& MotionEvent.ACTION_MASK的原因是屏蔽poniter index 這些信息,因爲這裏不需要,只需要知道Action的類型,等同於MotionEvent的getActionMasked().
    • 如果發現是ACTION_DOWN,那麼說明是新一輪的Touch事務的開始, 因爲一次完整的Touch事務必然以down開始,而且也只能有一次down,調用cancelAndClearTouchTargets(ev)來將之前所有的相關state全部clear, 將有可能會將上一次Touch事務的up/cancel event drop掉, 而resetTouchState()則被調用來重置所有的Touch相關變量和狀態(包括清除了所有的TouchTarget)
    • 如果是ACTION_DOWN或者mFirstTouchTarget不是null,對於ActionDown, 必然是一輪MotionEvent的第一個,因此需要嘗試進行intercept, 而如果mFirstTouchTarget不是null代表着已經有ChildView聲明自己會接受這一輪的事務了,因此也需要進行intercept的嘗試(從這裏可以推斷出,如果在DOWN的時候,childView沒有響應者(導致mFirstTouchTarget == null),那麼本輪之後的MotionEvent都會直接走到另外一個分支: intercepted直接設置爲true, 導致不會再嘗試遍歷ChildView進行dispatch,ChildView永遠失去了對本輪MotionEvent的知曉), 那麼會嘗試進行InterceptTouchEvent, 不過之前要做一次disallowIntercept的判斷(mGroupFlags & FLAG_DISALLOW_INTERCEPT, viewgroup的requestDisallowInterceptTouchEvent()方法改變的就是這個flag),, 如果允許,就會調用onInterceptTouchEvent(ev)這個經常被使用的函數, 並且會將結果賦給intercepted, 傳入的MotionEvent的Action會被強制restore,以防止onInterceptTouchEvent中修改了action的類型
    • 如果!disallowIntercept,那麼intercepted = false
    • 如果當前沒有任何的touch target(mFirstTouchTarget == null),並且action 也不是一個DOWN, 那麼說明之前DOWN的motionEvent沒有被其他的ChildView所處理,那麼這一輪的Event都會直接交給ViewGroup本身來處理,即也是”intercept”了,代表的意思就是不用再對childView進行相關的判斷了,因此parent已經將這輪MotionEvent吃掉了.
    • canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL, 即如果PFLAG_CANCEL_NEXT_UP_EVENT這個標誌位被設置了,那麼這一輪MotionEvent也會被強制cancel.
    • split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
    • 如果沒有被cancel,也沒有被intercept,這意味着該MotionEvent是有可能傳遞給ChildVide的,因此下面就是對ChildView的遍歷處理:
      • 如果是ACTION_DOWN, 或者split && actionMasked = MotionEvent.ACTION_POINTER_DOWN 或者actionMasked = MotionEvent.ACTION_HOVER_MOVE,對按下的特殊邏輯處理, 這是這一輪裏的第一個事件,誰最終接收了它就基本上就確定了這一輪最終的處理位置.
      • 獲取ActionIndex, removePointersFromTouchTargets(idBitsToAssign)
      • 下面的操作就是將挨個遍歷當前的Child(getChildDrawingOrder()可以改變遍歷ChildView的順序)(* 對於不可見或者及當前有動畫的(canViewReceivePointerEvents(..))或者當前的落點不在範圍內的(isTransformedTouchPointInView(…), 會考慮parentView的scrollX/Y的影響)ChildView直接ignore*), 將可以dispatch的Touch Event進行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign), 如果返回true,代表這個ChildView聲明自己會處理這一次的TouchEvent,那麼會直接break出去childView的遍歷處理,並且newTouchTarget = addTouchTarget(child, idBitsToAssign), 將這個child保存爲TouchTarget, 在這一輪後續的MotionEvent到來時,這個TouchTarget會被check以能將後續的MotionEvent直接投遞給已經宣稱自己接受這一輪MotionEvent的ChildView
      • 如果發現newTouchTarget == null(及這次沒有一個child可以被dispatch此TouchEvent, 那麼和會找到之前最後一次添加的newTouchTarget)
    • 如果mFirstTouchTarget == null,即沒有任何的TouchTarget可以來進行dispatch此MotionEVent,那麼會調用自己的dispatchTransformedTouchEvent(ev, canceled, null(如果是ChildView話,這裏就填入ChildView引用),
      TouchTarget.ALL_POINTER_IDS)
    • 否則挨個遍歷當前的TouchTarget(其實是一個鏈表, 以mFirstTouchTarget爲頭), 這表明,一個MotionEvent是可以傳遞給多個ChildView的, 對於在上面已經dispatch過Event的ChildView(一般會被加爲newTouchTarget)會直接pass處理下一個, 向TouchTarget指定的View調用dispatchTransformedTouchEvent(…),還會順帶檢測cancelChild = resetCancelNextUpFlag(target.child) || intercepted, 這個flag的使用說明了如果parentView在一輪Event過程中途intercept了ChildView的MotionEvent(即這一輪之前的MotionEvent給了ChildView處理), 那麼ChildView就會收到一個ACTION_CANCEL, 如果要cancel, 會將該TouchTarget從鏈表中remove並回收TouchTarget.
  2. ViewGroup的dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits):

    • 如果child是null,那麼調用super.dispatchTouchEvent(event)(即View定義的dispatchTouchEvent(event))
      *否則調用 child.dispatchTouchEvent(event)
    • 如果是childView,那麼還會將ViewGroup的ScrollX/Y考慮進去,對要dispatch的MotionEvent的X/Y進行累加來的到肉眼觀察上合理的X/Y
  3. View的boolean dispatchTouchEvent(MotionEvent event):

    • 註釋裏解釋了返回值的意義: True if the event was handled by the view, false otherwise.
    • 對於輸入的event, 會先進性onFilterTouchEventForSecurity(event)過濾:
      • 如果mViewFlags中有FILTER_TOUCHES_WHEN_OBSCURED並且輸入的MotionEvent的Flag也有FLAG_WINDOW_IS_OBSCURED這個標記,那麼返回false, 代表這個MotionEvent不進行處理,dispatchTouchEvent(…)會直接返回false直接放棄對這一輪MotionEvent的處理.
    • 如果通過了FilterSecurity, 那麼會優先處理mOnTouchListener(在ViewENABLED的前提下),如果mOnTouchListener的onTouch(…)函數返回true, 那麼也直接返回true表明接手這一輪MotionEvent, 否則還會交給onTouchEvent(event)
      • 如果onTouchEvent(event)還能返回true的話,還會接手這一輪MotionEvent.
      • 否則如果mOnTouchListener和onTouchEvent(event)都返回了false, 那麼代表View放棄對這一輪MotionEvent的處理,返回false.
  4. ViewRootImpl在收到Event的時候,調用的是DecorView的dispatchPointerEvent(MotionEvent event)(通過processPointerEvent(QueuedInputEvent q)):

    • 如果MotionEvent是event.isTouchEvent(), 那麼調用DecorView的 dispatchTouchEvent(event)(再次驗證了dispatchTouchEvent的入口地位), 入口應該是DecorView的dispatchTouchEvent, 但是後面會看到,它在大多數情況也僅僅是無腦轉發.
    • 否則調用View的dispatchGenericMotionEvent(event).
  5. DecorView本身extends FrameLayout, 並且也對dispatchTouchEvent(…)進行了簡單的修改:

    • getCallback()
    • 如果是有callback,並且沒有isDestroyed()並且mFeatureId < 0(Feature = -1代表它是Application的 DecorView)纔會轉而調用cb.dispatchTouchEvent(ev)
    • 否則直接調用super.dispatchTouchEvent(ev)(FrameLayout沒有修改該函數,因此這個函數將是ViewGroup的dispatchTouchEvent(ev))
    • 上面的getCallback()和isDestroyed()均是Window類中定義的函數.
    • 那麼正常啓動一個Activity時,會在被attach的時候通過PolicyManager.makeNewWindow(this)來生成一個PhoneWindow, 並且將其callback改爲Activity自己, Activity實現了Window.Callback接口
    • 那麼接着上面的邏輯,在普通的Activity中,DecorView的dispatchTouchEvent(ev)會轉發給Window Callback(這裏是Activity)的dispatchTouchEvent(..)
    • Activity的dispatchTouchEvent(..):
      • 如果MotionEvent的是ACTION_DOWN, 那麼會調用onUserInteraction()代表用戶現在開始交互了
      • 如果getWindow().superDispatchTouchEvent(ev)返回true, 那麼callback返回true代表這一次的MotionEvent被處理了,注意這時候尚未進入dispatch體系流程,因此不是一輪
      • 如果返回false, 那麼返回Activity的onTouchEvent(ev).
      • Activity本身的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., DIalog的點擊區域外自動關閉其實跟這個差不多):
        • 如果Activity的mWindow的shouldCloseOnTouch(this, event))返回true,那麼調用finish(),並且返回true,因爲這個event是被處理了的.
          • Window的shouldCloseOnTouch(this, event))函數:
            • 如果設定了mCloseOnTouchOutside
            • 如果event是ACTION_DOWN
            • 如果isOutOfBounds(context, event)
            • 如果peekDecorView(..)返回爲非空表明現在是有一個DecorView的.
            • 那麼就會返回true表明可以關閉這個Activity.
        • 否則返回false.
      • 再看看還可能被調到的Window的superDispatchTouchEvent(..):
        • 抽象函數,具體實現看PhoneWindow: 調用了DecorView的superDispatchTouchEvent(…), DecoreView進而調用了super.dispatchTouchEvent(…), 饒了一圈,終於進入了dispacth邏輯體系了,Activity中間橫插了一腳,其實Activity等於加了一個預處理(onUserInteraction)和一個後處理(Activity的onTouchEvent)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章