關於Android觸摸事件分發的原理詳析
一:前言
最近在學Android的觸摸事件分發,我覺得網上說的太雜太亂,而且有很多博客都有明顯的錯誤。什麼自頂向下分發,自下向頂分發,什麼攔截又一直消費什麼什麼之類,非常難懂。為瞭自己將來回顧可以更好的理解這塊知識,也為瞭後來之人可以更好的學習,我寫下這篇博客。
二:說在前面的知識
- 點擊,滑動,松手都是由MotionEvent這個類來表示。
- 屏幕上的一個事件序列是指以一個MotionEvent.action_down按下開始,以若幹個MotionEvent.action_move移動事件在中間,再以一個MotionEvent.action_up作為結束的事件流。
- view group是view的子類。view group和view都有dispatchTouchEvent方法;view group有onTnterceptTouchEvent和onTouchEvent方法,view 隻有onTouchEvent方法。
三:整體流程
1:activity
我們點擊屏幕的所有事件,都會被第一個接收。
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();//是一個空方法,如果想知道按下瞭屏幕,可以重寫這個方法打印日志 } if (getWindow().superDispatchTouchEvent(ev)) {//把這個事件傳給window屬性 return true; } return onTouchEvent(ev); }
2:window就是PhoneWindow
每一個activity都會對應一個PhoneWindow(在onCreate方法之前、activity內部的attach方法中創建)。PhoneWindow含有一個decor view屬性(setContentView中創建),phone window把事件傳給decor view。 decor view繼承於view group。點擊事件現在傳到decor view這裡,就開始view group的事件分發邏輯瞭。
3:view group
view group收到點擊事件, 進入dispatchTouchEvent, 如果滿足以下二個條件中的任何一個條件:
- 事件為down事件
- 有一個子view或子view group在處理著事件流瞭
mFirstTouchTarget !=null
就進入判斷,如果沒有被禁用攔截(子view調用parent.requestDisallowed….)就執行, onInterceptTouchEvent代碼。
如果決定攔截,後面還會把mFirstTouchTarget置為null,這樣,之後就不會在調用onInterceptTouchEvent瞭。而且之後的事件流都會由這個view group的dispatchTouchEvent處理
如果不決定攔截,就遍歷子view、子view group,挨個調用它們的dispatchTouchEvent。如果沒有人接收,那就調用自己的super.dispatchTouchEvent. view group的super.dispatchTouchEvent就是自己view那部分 的 dispatchTouchEvent。
4:view
在view這一層,對於down事件,返回true就表示消費這個down事件之後的序列。具體看圖。
view調用setOnTouchLIstener可以設置OnTouchListener,重寫onTouch方法。從源碼中可以看出,若onTouch返回true,將不再回調onTouchEvent方法。不回調onTouchEvent的話,那onClickListener也不能回調瞭。
四:一些關鍵點
即使有view消費著一組事件,事件流由底向上傳遞時,依然會調用每一個view group的intercept攔截方法判斷是否攔截。當一個view group遍歷它所有的子view沒有一個接收時,就會進入view模式,調用自己繼承於view的那一個dispatchTouchEvent方法。如果自己不接收,那會交給調用自己的dispatchTouchEvent的那個父view.
事件流沒有什麼自上而下,就是自下而上的。
ViewGroup的實現負責將觸摸事件沿著控件樹向子控件進行派發,而View的實現則主要用於事件接收與處理工作。當view group沒有子view接收時,view group作為一個“view”去處理。
五:從源碼看觸摸事件分發
由於專欄關註自定義控件,所以關於系統如何從硬件獲取觸摸事件以及傳遞到Activity的dispatchTouchEvent就不詳細分解,下面將從Activity的dispatchTouchEvent方法來一步步看事件是如何被分發傳遞的:
Activity中的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
其中onUserInteraction();是一個空實現,是系統留給我們的一個修改事件分發的一個方法,這裡可以忽略。
所以實際上Activity的dispatchTouchEvent方法是調用的PhoneWindow的superDispatchTouchEvent方法,如果superDispatchTouchEvent返回false,沒有消費掉事件,那麼才會再交給activity的onTouchEvent方法去處理,從這個角度來講,如果所有地方都沒有消費掉事件,最後接收事件的會是Activity的onTouchEvent方法。
那麼下面我們來看看PhoneWindow中的superDispatchTouchEvent方法:
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
發現實際上調用的是DecorView對象mDecor的superDispatchTouchEvent方法,來看看DecorView的superDispatchTouchEvent方法:
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
調用的super.dispatchTouchEvent,而再來看看這個DecorView的繼承關系:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
所以調用的是FrameLayout中的dispatchTouchEvent方法,而FrameLayout並沒有重寫dispatchTouchEvent方法,所以實際調用的是FrameLayout的父類 —> ViewGroup中的dispatchTouchEvent方法,下面這個圖描述瞭從系統得到MotionEvent實際到傳遞給DecorView的super.dispatchTouchEvent的過程:
總結
到此這篇關於Android觸摸事件分發原理的文章就介紹到這瞭,更多相關Android觸摸事件分發原理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Android 深入探究自定義view之事件的分發機制與處理詳解
- Android事件分發機制全面解析
- View事件分發原理和ViewPager+ListView嵌套滑動沖突
- Android那兩個你碰不到但是很重要的類之ViewRootImpl
- Android自定義View實現拖動自動吸邊效果