Android事件分發
在android 界一直流傳著一句話,誰能掌握view,誰就能掌握android(這是我自己裝比說的)
不過view 這一塊在android中 確實是 由普通android 向高階android 進發的必須要走的一步路,我準備 一邊學,一邊記錄自己 學習view 的歷程,今天 先來講講 view 的事件分發機制--
先看一張圖:
這張圖大家應該都很熟悉, 所謂的事件分發機制 無非就是 view的 相關觸控事件的傳遞過程,
比如我點選一下螢幕,圖中各view 的 觸控事件分別是如何 響應的,分別有哪些事件會得到響應,
廢話不多話,程式碼擼起來:
Viewgroup:A
public class OneView extends LinearLayout { public OneView(Context context) { super(context); } public OneView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e("TEST", "OneView dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e("TEST", "OneView onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.e("TEST", "OneView onTouchEvent"); return super.onTouchEvent(event); } }
Viewgroup:B
public class TwoView extends LinearLayout { public TwoView(Context context) { super(context); } public TwoView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e("TEST", "TwoView dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e("TEST", "TwoView onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.e("TEST", "TwoView onTouchEvent"); return super.onTouchEvent(event); } }
View:C
public class ThreeView extends View { private Paint paint; private int Withs; private int Hiths; public ThreeView(Context context) { super(context); } public ThreeView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); Withs = w / 2; Hiths = h / 2; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint = new Paint(); paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.FILL); //設定畫筆模式為填充 paint.setStrokeWidth(10f); //設定畫筆寬度為10px paint.setTextSize(30); canvas.drawText("我是帥比", Withs, Hiths, paint); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e("TEST", "ThreeView dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.e("TEST", "ThreeView onTouchEvent"); return super.onTouchEvent(event); } }
當我點金 C 的時候,事件的呼叫log分別為:
很清楚 ,呼叫流程依次為: 最外層view A:dispatchTouchEvent---> onInterceptTouchEvent ---->..........
如果這個點選事件從最外層到最裡層都沒有被 攔截消費(即return true),則會繼續從最底層的view 的 onTouchEvent 事件 往上層的 onTouchEvent 事件傳遞,直到被消費,如果 上層也沒有消費,則此次點選事件就被流放,說到這裡,其實 這並不是 所有的佈局,在我們 肉眼到的佈局之外,還包著有另外的佈局,比如 標題欄什麼的,那麼這些view 在哪裡:
如下圖:
Rootview 相當於我們佈局的根佈局 LinearLayout,在其之上 的View結構中莫名多出來的兩個東西,PhoneWindow
和 DecorView
,這兩個我們並沒有在Layout檔案中定義過,但是為什麼會存在呢?
仔細觀察上面的 layout 檔案,你會發現一個問題,我在 layout 檔案中的最頂層 View(Group) 的大小並不是填滿父窗體的,留下了大量的空白區域,由於我們的手機螢幕不能透明,所以這些空白區域肯定要顯示一些東西,那麼應該顯示什麼呢?
有過安卓開發經驗的都知道,螢幕上沒有View遮擋的部分會顯示主題的顏色。不僅如此,最上面的一個標題欄也沒有在 layout 檔案中,這個標題欄又是顯示在哪裡的呢?
你沒有猜錯,這個主題顏色和標題欄等內容就是顯示在
DecorView
中的。
現在知道 DecorView
是幹什麼的了,那麼PhoneWindow
又有什麼作用?
要了解 PhoneWindow 是幹啥的,首先要了解啥是 Window ,看官方說明:
Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
簡單來說,Window是一個抽象類,是所有檢視的最頂層容器,檢視的外觀和行為都歸他管,不論是背景顯示,標題欄還是事件處理都是他管理的範疇,它其實就像是View界的太上皇(雖然能管的事情看似很多,但是沒實權,因為抽象類不能直接使用)。
而 PhoneWindow 作為 Window 的唯一親兒子(唯一實現類),自然就是 View 界的皇帝了,PhoneWindow 的權利可是非常大大,不過對於我們來說用處並不大,因為皇帝平時都是躲在深宮裡面的,雖然偶爾用特殊方法能見上一面,但想要完全指揮 PhoneWindow 為你工作是很困難的。
而上面說的 DecorView 是 PhoneWindow 的一個內部類,其職位相當於小太監,就是跟在 PhoneWindow 身邊專業為 PhoneWindow 服務的,除了自己要幹活之外,也負責訊息的傳遞,PhoneWindow 的指示通過 DecorView 傳遞給下面的 View,而下面 View 的資訊也通過 DecorView 回傳給 PhoneWindow。
這些額外的就就到這裡,有興趣的可以自行谷歌,我們繼續 我們的事件分發機制,
如最上面在沒有任何攔截的時候,事件分發可謂是一馬平川,那麼如果我們想搞點事情呢,
比如,我在B中攔截掉這個點選事件,我們看看 事件是如何分發的,
如圖我在 onInterceptTouchEvent 中直接攔截了點選事件,那麼現在的傳遞是怎麼樣呢?
如下:
果然 事件沒有繼續往下傳遞到C,直接觸發了 B view 的onTouchEvent,由於 B中的 onTouchEvent 也沒有消費此次點選事件,故會繼續往上層的 onTouchEvent 事件傳遞,
如果我們 在B 的onTouchEvent 中 消費了此次事件呢,又會如何呢,
我們把 如圖:
onTouchEvent 的返回改為true,表示消費此次點選事件,結果呼叫方法如下:
onTouchEvent 走了三次,為什麼會走三次,因為
onTouchEvent有3個回掉,分別為
MotionEvent.ACTION_DOWN:
MotionEvent.ACTION_MOVE:
MotionEvent.ACTION_UP:
按下,移動,擡手,
講到這裡,我想大家基本明白事件的分發機制是如何的了