一.案例簡述
在Android開發中,事件分發機制是一塊Android比較重要的知識體系,也是比較難理解的,網上很多資料都是用流程圖的方式介紹事件分發的流程以及注意事項或者以部分源碼的方式進行介紹,這樣不能很好的幫助我們從整體上了解事件分發的過程更不能深入的從代碼層次掌握整個分發的具體過程,在遇到自定義view或者事件衝突的問題的時候不能準確的找到問題所在,下面我們從源碼上完整的分析和講解事件分發整個過程,從代碼層次上深入理解。
二.案例分析
1.概念
首先介紹一下事件分發的基本概念便於後面分析源碼時使用。
事件分發的對象:
點擊事件
事件類型:
1. MotionEvent.ACTION_DOWN 按下View(所有事件的開始)
2. MotionEvent.ACTION_UP 擡起View(與DOWN對應)
3. MotionEvent.ACTION_MOVE 滑動View(中間可能產生無數個MOVE事件)
4. MotionEvent.ACTION_CANCEL 結束事件(非人爲原因)
注意事項:
MotionEvent.ACTION_CANCEL這個事件是非人爲觸發的,比如一個事件列,DOWN事件先傳遞到子View,但是當MOVE事件先傳遞到父View時,父View攔截了該事件,這樣MOVE事件無法傳遞到子View,這個時候父View會傳遞一個CANCEL事件給子View,代表這個事件列傳遞到子View結束。
事件列:
從手指接觸屏幕至手機離開屏幕,這個過程產生的一系列事件。
點擊事件流程:
一般情況下,事件都是以DOWN事件開始,UP事件借宿,中間有無數個MOVE
事件,如下圖:
圖1.點擊事件流程
事件分發的本質:
將點擊事件傳遞到某個具體的View以及處理的整個過程
事件在哪些對象之間進行傳遞:
Activity、ViewGroup、View
所以要想充分理解Android分發機制,本質上是要理解:
1.Activity對點擊事件的分發機制
2.ViewGroup對點擊事件的分發機制
3.View對點擊事件的分發機制
接下來我們會先從事件分發的最開始對象Activity講起,然後傳遞到ViewGroup,最後到View的過程。
2.Activity事件分發
想要了解Activity的事件傳遞,首先我們需要對Activity的界面架構充分了解。下圖是Activity的界面架構。
圖2.Activity界面架構
每個Activity都包含一個Window對象,在Android中Window對象基本上有PhoneWindow來實現,PhoneWindow將一個DecorView設置成爲整個應用窗口的根View。
DecorView作爲窗口界面的頂層視圖,它將屏幕分成兩部分一個是TitleView,一個是ContentView。ContentView是一個id爲Content的FrameLayout我們平時setContentView的layout佈局就是設置在這裏面的。下面直接看源碼來分析看到如上的源碼大家就知道了一個Activity包含一個window對象,這個對象是由PhoneWindow來實現的,PhoneWindow將DecorView做爲整個應用窗口的根View,而這個DecorView又將屏幕劃分爲兩個區域一個是TitleView一個是ContentView,而我們平常做應用所寫的佈局正是展示在ContentView中的。
下面是Android的根佈局有很多種類,但是基本上大致框架一樣,我們就以最普通的一種screen_simple.xml來分析,我們看一下源碼。因爲本文不是主要講解Android佈局加載所以省略Activity加載過程直接找到加載的佈局。
圖3.Activity跟佈局View
介紹了Activity的根佈局之後我們直接進入Activity事件分發過程,首先一個點擊事件最先進入的就是Activity的dispatchTouchEvent()方法,我們首先進入dispatchTouchEvent()方法中查看源碼來了解整個過程。
圖4.Activity的dispatchTouchEvent()
分析1:這個方法點擊進入後可以看見裏面沒有實際代碼,只是一個回調,可以在Activity中重寫來監聽用戶和程序之間是否處於交互狀態。
分析2:如果返回true說明這個事件已經被ViewGroup或者View消耗掉了不會繼續執行下面的方面,如果返回false則會進入分析3中,
分析3:這個時候就會調用Activity自己的onTouchEvent方法來進行處理
接下來我們先進入分析2的方法實例中進行分析,首先getWindow()獲取到的是Activity的window對象,我們上面介紹到Activity的window對象就是PhoneWindow,PhoneWindow是Window的實現類,這個時候我們可以直接進入PhoneWindow中查看superDispatchTouchEvent方法進行分析。
圖5.PhoneWindow中superDispatchTouchEvent方法
可以看見這個方法又是調用mDecor的superDispatchTouchEvent()方法,這裏的mDecor就是我們上面介紹Activity架構時PhoneWindow中的內部類DecorView,我們繼續查看DecorView中superDispatchTouchEvent()方法。
圖6.DecorView中superDispatchTouchEvent方法
分析1: 此處可以看到調用了父類ViewGroup的dispatchTouchEvent()方法這個時候就實現了Activity將事件傳遞到ViewGroup。這個時候就實現了Activity到ViewGroup的事件傳遞。如果這裏返回true就代表ViewGroup或者ViewGroup下面的子View消費了事件,事件分發結束,如果返回false代表ViewGroup以及ViewGroup下面的子View沒有消耗該事件,所以這個時候就會調用圖4中Activity的onTouchEvent()方法。
我們分析一下如果這裏返回的是false,Activity調用onTouchEvent()方法的具體實現。
圖7.Activity的onTouchEvent()方法
分析1:判斷該點擊事件是否是發生在window邊界之外的觸摸事件,是在抽象類Window中實現,是的話就返回true不是就返回false,一般情況下都返回false。
分析到這裏Activity的事件分發過程就已經結束,我們用流程圖的方式整理一下流程。
圖8.Activity事件分發流程圖
3.ViewGroup事件分發
Activity事件分發到ViewGroup後就會由ViewGroup來進行分發,下面我們來介紹一下圖5分析1中調用ViewGroup中的dispatchTouchEvent()方法。
圖9.ViewGroup中dispatchTouchEvent()方法
分析1:intercepted代表是否攔截事件,攔截代表不分發到子View,不攔截就會直接傳遞給子View。
分析2:disallowIntercept = 是否禁用事件攔截的功能(默認是false),可通過調用requestDisallowInterceptTouchEvent()修改
分析3:是否重寫onInterceptTouchEvent()方法,默認是false,如果重寫返回true代表攔截事件分發到子View
接着往下看如果不攔截事件分發到子View也就是intercepted賦值爲false。
圖10.ViewGroup中dispatchTouchEvent()方法
分析1:分發給子View是否成功,默認是false,後面會通過這個判斷是否分發到子View成功並且子View成功消耗事件。
分析2:是否重寫onInterceptTouchEvent()方法或者子View調用requestDisallowInterceptTouchEvent(),如果沒有重寫進入if判斷
分析3:遍歷子View查找點擊事件是否在子View中
分析4:canViewReceivePointerEvents(child)方法判斷是否View可見並且沒有播放動畫
isTransformedTouchPointInView表示點擊事件的座標是否落在View的範圍內。
分析5:事件分發到子View或者調用viewGroup父類的dispatchTouchEvent()
接着分析5我們可以看到怎麼從ViewGroup分發到子View,以及如果該點擊事件不在任何一個子View中而是在ViewGroup中我們怎麼處理這個點擊事件。下圖是分析5中的dispatchTransformedTouchEvent()的方法內部實現。
圖11.dispatchTransformedTouchEvent()方法
分析1:如果沒有找到點擊事件所在的子View也就是child方法爲null則調用父類的dispatchTouchEvent()方法,因爲ViewGroup的父類是View也就是調用父類View的dispatchTouchEvent()方法接着會執行判斷是否實現onTouch()以及onTouchEvent()方法,這裏是和子View的實現方式事件消耗是一樣的我們再子View中進行講解,如果返回false就會將事件傳遞到Activity讓Activity自己來處理,如果找到了點擊事件所在的子View這時就會調用子View的dispatchTouchEvent()方法將事件傳遞給子View也就是完成了ViewGroup到View的事件分發。
接下來我們用一張流程圖來看一下ViewGroup的實現過程,清晰的整理一下整個ViewGroup的事件分發過程。
圖12.ViewGroup事件分發流程圖
4.View事件分發
ViewGroup將事件分發到子View後也就是調用子View的dispatchTouchEvent()方法,我們分析一下最底層View是怎麼消費這個事件的。
圖13.View的dispatchTouchEvent()方法
分析1:首先判斷該View有沒有使用setOnTouchListener監聽,這裏可以看見OnTouchListener是優先於onClickListener的,ENABLED_MASK代表是否可以點擊,大部分View默認是可以點擊的所以爲true
分析2:如果mOnTouchListener.onTouch()返回true事件結束,如果返回false代表子View沒有消費這個事件,事件將會向上傳遞給它的ViewGroup
子View的onTouchEvent()方法我們就看一下兩個地方分析一下即可。
圖14.子View的onTouchEvent()方法
分析1:這裏可以看見在獲取到ACTION_UP擡起手勢時會調用performClick()這個方法內部實現就是我們經常看到的onClick()的回調方法調用,也就是點擊事件監聽實現的調用。
分析2:可以看見switch方法最外面會返回true也就是表示只要該事件可以點擊就返回true代表消費了該事件,如果不能點擊就返回false將事件向上傳遞給父View來處理。
分析到這裏我們的View事件也已經全部分析了,我們還是以一張流程圖來看一下整個View事件傳遞處理的流程。
圖15.View事件分發流程圖
5.事件分發總體流程
最後總體回顧一下整個事件分發從Activity到ViewGroup到View的過程,如下圖。
圖16.事件分發整體流程
我們看到事件分發是從Activity一層層的分發到最內層的View,只要不攔截就會調用dispatchTouchEvent()方法一層層向下分發,分發完成後處理事件也是一樣,從最裏層的View開始如果最內層View不處理就會向上傳遞,如果ViewGroup也不處理最後會傳遞到Activity這個時候事件相當於沒有控件處理。
三.案例總結
通過上面的敘述,相信大家對Android的分發機制有了比較細緻的理解。爲了加深大家的理解,下面做個簡單的總結。
ViewGroup默認不攔截任何事件。
點擊事件的分發過程如下:dispatchTouchEvent—>onTouchListener的OnTouch方法—>onTouchEvent—>onClickListener的onClick方法。從而也可以看出onTouch優先於onClick執行。
子View可以通過使用getParent().requestDisallowInterceptTouchEvent(true),阻止ViewGroup對其MOVE或UP事件進行攔截。
一個點擊事件產生後,傳遞過程是:Activity—>ViewGroup—>View。頂級View接受到事件後,就會按照上面的規則去分發事件。