前言
當Android系統捕獲到用戶的各種輸入事件後,如何準確地傳遞給真正需要這個事件的控件呢?Android給我們提供了一整套完善的事件傳遞、處理機制,來幫助開發者完成準確的事件分配和處理。
基礎知識
- MotionEvent
- 所有觸摸事件都被封裝成MotionEvent對象,包括Touch的位置、時間、歷史記錄以及第幾個手指(多指觸摸)等。
- 一次完整的MotionEvent事件,是從用戶觸摸屏幕到離開屏幕。整個過程的動作可如示:ACTION_DOWNACTION_DOWN(1次)-> ACTION_MOVE(N次)- > ACTION_UP(1次)
- 多點觸摸,每一個觸摸點Pointer會有一個id和index。對於多指操作,通過pointerindex來獲取指定Pointer的觸屏位置。比如,對於單點操作時獲取x座標通過getX(),而多點操作獲取x座標通過getX(pointerindex)
- View
- View是所有視圖對象的父類,實現了動畫相關的接口Drawable.Callback, 按鍵相關的接口KeyEvent.Callback, 交互相關的接口AccessibilityEventSource。比如Button繼承自View。
- TouchEvent事件處理相關的方法:
dispatchTouchEvent(MotionEvent event)
onTouchEvent(MotionEvent event)
- ViewGroup
- ViewGroup,是一個abstract類,一組View的集合,可以包含View和ViewGroup,是所有佈局的父類或間接父類。繼承了View,實現了ViewParent(用於與父視圖交互的接口), ViewManager(用於添加、刪除、更新子視圖到Activity的接口)。比如常用的LinearLayout,RelativeLayout都是繼承自ViewGroup。
- TouchEvent事件處理相關的方法:
dispatchTouchEvent(MotionEvent event)
onInterceptTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent event)
- Activity
- Activity是Android四大基本組件之一,這裏只介紹Activity與touch相關的方法。當手指觸摸到屏幕時,屏幕硬件一行行不斷地掃描每個像素點,獲取到觸摸事件後,從底層產生中斷上報。再通過native層調用Java層InputEventReceiver中的dispatchInputEvent方法。經過層層調用,交由Activity的dispatchTouchEvent方法來處理。
- TouchEvent事件處理相關的方法:
dispatchTouchEvent(MotionEvent event)
onTouchEvent(MotionEvent event)
流程圖
當用戶觸摸屏幕時會產生一系列MotionEvent,那麼Activity,ViewGroup,View之間是如何處理呢?這裏通過一張圖來從全局性描述整個流程
結論
流程圖總結
onInterceptTouchEvent返回值true表示事件攔截, onTouch/onTouchEvent 返回值true表示事件消費。
觸摸事件先交由Activity.dispatchTouchEvent。再一層層往下分發,當中間的ViewGroup都不攔截時,進入最底層的View後,開始由最底層的OnTouchEvent來處理,如果一直不消費,則最後返回到Activity.OnTouchEvent。
ViewGroup纔有onInterceptTouchEvent攔截方法。在分發過程中,中間任何一層ViewGroup都可以直接攔截,則不再往下分發,而是交由發生攔截操作的ViewGroup的OnTouchEvent來處理。
子View可調用requestDisallowInterceptTouchEvent方法,來設置disallowIntercept=true,從而阻止父ViewGroup的onInterceptTouchEvent攔截操作。
OnTouchEvent由下往上冒泡時,當中間任何一層的OnTouchEvent消費該事件,則不再往上傳遞,表示事件已處理。
如果View沒有消費ACTION_DOWN事件,則之後的ACTION_MOVE等事件都不會再接收。
只要View.onTouchEvent是可點擊或可長按,則消費該事件.
onTouch優先於onTouchEvent執行,上面流程圖中省略,onTouch的位置在onTouchEvent前面。當onTouch返回true,則不執行onTouchEvent,否則會執行onTouchEvent。onTouch只有View設置了OnTouchListener,且是enable的才執行該方法。
區分總結
View觸摸事件傳遞機制:
- 觸摸控件(View)首先執行dispatchTouchEvent方法。
- 在dispatchTouchEvent方法中先執行onTouch方法,後執行onClick方法
- 如果控件(View)的onTouch返回false或者mOnTouchListener爲null(控件沒有設置setOnTouchListener方法)或者控件不是enable的情況下會調運onTouchEvent,dispatchTouchEvent返回值與onTouchEvent返回一樣。
- 如果控件不是enable的設置了onTouch方法也不會執行,只能通過重寫控件的onTouchEvent方法處理(上面已經處理分析了),dispatchTouchEvent返回值與onTouchEvent返回一樣。
- 如果控件(View)是enable且onTouch返回true情況下,dispatchTouchEvent直接返回true,不會調用onTouchEvent方法。
- 當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發下一個action(也就是說dispatchTouchEvent返回true纔會進行下一次action派發)。
ViewGroup觸摸事件傳遞機制:
- Android事件派發是先傳遞到最頂級的ViewGroup,再由ViewGroup遞歸傳遞到View的。
- 在ViewGroup中可以通過onInterceptTouchEvent方法對事件傳遞進行攔截,onInterceptTouchEvent方法返回true代表不允許事件繼續向子View傳遞,返回false代表不對事件進行攔截,默認返回false。
- 子View中如果將傳遞的事件消費掉,ViewGroup中將無法接收到任何事件。
Activity觸摸事件傳遞機制:
- 首先會觸發Activity的dispatchTouchEvent方法
- dispatchTouchEvent方法中如果是ACTION_DOWN的情況下會接着觸發onUserInteraction方法。
- 接着在dispatchTouchEvent方法中會通過Activity的root View(id爲content的FrameLayout),實質是ViewGroup,通過super.dispatchTouchEvent把touchevent派發給各個activity的子view,也就是我們再Activity.onCreat方法setContentView時設置的view。
- 若Activity下面的子view攔截了touchevent事件(返回true)則Activity.onTouchEvent方法就不會執行