ps:本博客是筆者本身根據官方開發文檔一步一步本身學習記錄下來的過程,可能會有點錯誤,不過我感受大部分應該是正確的,中途思惟有點跳躍,最後由於實踐的時候遇到一點小問題,致使我當時都在懷疑本身,因此又多看了幾遍文檔的函數介紹。前端
說到事件分發機制我第一反應會想到Dom的事件流,早期的IE和網景的事件流是相反的。(IE的事件流是咱們經常據說的事件冒泡,從下至上;網景的事件流則相反,爲事件捕獲)android
在如今瀏覽器都使用的Dom2事件監聽方法中,實現了事件的捕獲與事件冒泡,但只能二選一,默認爲事件冒泡,因此咱們常常聽到的說法是事件冒泡。程序員
再來講Android,在Android中咱們稱這爲事件的分發機制,他的分發機制與dom2很類似,不過是它兩個事件都啓用,就是一個U型的分發機制。瀏覽器
我上開發文檔中去搜索了一下「dispatchTouchEvent」,發現「ViewGroup、Activity、Dialog、View」 中有着如出一轍的一個方法。bash
先無論上面的那幾個,咱們查看一下第四個官方文檔, 「Manage touch events in a ViewGroup | Android Developers」 看標題他正好是咱們想要去了解的。dom
每當在ViewGroup的表面上檢測到觸摸事件時(包括其子級的表面),都會調用 onInterceptTouchEvent() 方法。若是 onInterceptTouchEvent() 返回true,則將MotionEvent攔截,這意味着它不會傳遞給子級,而是傳遞給父級的onTouchEvent()方法。ide
該onInterceptTouchEvent()方法使父母有機會在其孩子以前看到任何觸摸事件。若是true從返回onInterceptTouchEvent(),則之前處理觸摸事件的子視圖將收到ACTION_CANCEL,而且從該點開始的事件將發送到父級的onTouchEvent()方法進行常規處理。onInterceptTouchEvent()當false事件沿視圖層次結構行進到一般的目標時,它們也能夠返回並監視事件,事件將由其本身處理 onTouchEvent()。函數
才接觸事件分發(原來接觸過一次是在ViewPage製做輪播圖的時候,如今可使用ViewPage2了),看完第一段真是一頭霧水,本身或子集的onInterceptTouchEvent()方法?父級的onTouchEvent()方法?這一看就知道ViewGroup是一箇中間商或零售商的角色啊,這須要咱們瞭解一下Activity、ViewGroup、View 的關係了,正好是上面四大天團的三個。(Dialog咱們就無論了)學習
其實不少知識點是相通的,一步一步來,就像建造者模式同樣,日積月累,最後就會作出一份完美的蛋炒飯。上面這個關係圖呢是我在瞭解Activity的繪製流程的時候,根據當時整理的流程圖畫出來的。ui
有興趣的話能夠去了解一下Activity的啓動流程。扯遠了,後面我要是聯想到其餘地方去你們就將就複習或者預習一些知識點,再回到咱們的事件分發,從圖3中咱們能夠看到,一個用戶交互的頁面的最上層的是Activity,咱們的四大組件之一,咱們在AndroidManifest配置中註冊Activity的時候能夠加一個theme屬性(android:theme=「@android:style/Theme.Dialog」)將一個Activity設置爲窗口模式,因此咱們就先無論上面那個Dialog了,先把它看成一個Activity吧。(彈出Dialog就把它當成一個新的Activity,後面那個可見Activity處於onPause狀態,是不能進行交互的)
有同窗可能會問什麼是Android的事件分發機制? 我理解的事件分發機制就相似於在學校的項目組中,老師有了一個新需求。而後處理狀況會有幾種。(我先羅列出咱們等等會用到的幾種狀況) 老師會將這個需求告訴前端組組長與後臺組組長,組長根據這個需求去作安排。
前提:幾個視圖嵌套在一塊兒
問題:
這個操做事件(點擊、移動)被處理了(誰處理的?) ——> View(View是怎麼知道這個事件的?) ——> ViewGroup告訴他的(ViewGroup是怎麼知道這個事件的?) ——> Activity告訴他的(Activity怎麼知道的?) ——> 用戶點擊了屏幕 (倒回去看,這不就正是一個用戶點擊到系統處理的流程嗎?)
這個事件處理完了(View:我要告訴誰?) ——> ViewGroup(收到)——> Activity(收到)——> 用戶(哇,這個交互好棒)
結果:咱們將上面的合在一塊兒不就是一個完整的事件處理嗎?他的傳遞過程就是事件的分發機制。
圖示:Activity <==> ViewGroup <==> View
ps:我又想了一下最早知道用戶點擊事件的爲何不是那個View,爲何不是View收到事件處理完一層一層的返回上來,攔截是上層的對下層已經處理過的事件不進行上報。(這樣就和Dom事件流的冒泡事件同樣了) 我有個大膽的理解是,你在網頁中,你使用的是鼠標,鼠標是在屏幕裏面的,就能夠把它想成一個最底層的組件,只是z-index最高就好;而在手機中,點擊的通常是咱們手指,你只能點到屏幕,他最開始收到信號的只能是屏幕,而後只能根據圖3往下找,因此事件傳遞的順序應該就是上面那個圖示同樣。
android的事件分發最重要的三個方法是「dispatchTouchEvent()」、「onInterceptTouchEvent()」、「onTouchEvent()」,分發、攔截,處理與咱們上面🌰中的人物操做簡直如出一轍。
說了那麼多,都是本身根據大腦思惟的慣性去猜測的,還須要實際去驗證,咱們擼段Demo實踐一下。
角色:
Activity(老師)
ViewGroup(組長)
View(組員)
動做: 咱們去Activity、ViewGroup、View中查看一下這三個方法的介紹及用法。
到這咱們就瞭解了這三個方法的用途,以及一些注意事項,和我最初的想法有些不一樣(一開始我覺得,dispatchTouchEvent先監聽而後若是不是本身處理就分發,是的話就到onInterceptTouchEvent中去,onInterceptTouchEvent攔截就表示本身接受了,攔截後子控件就收不到消息了,就到onTouchEvent中開始工做了,onTouchEvent處理事件都在這,無論是接到任務,仍是作的過程,甚至作完,返回true就表明作完了)。如今改變下思路,人最可怕的就是知錯不改。
我從新整理了下對這三個方法的認識:
代碼:
Activity(MainActivity.kt)
class MainActivity : AppCompatActivity() {
val ROLE_TAG:String = "老師"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> Log.i(ROLE_TAG,"有個新需求,分配新需求")
}
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_UP -> Log.i(ROLE_TAG,"我靠!這個需求這麼南嗎?一我的都作不了?")
}
return super.onTouchEvent(event)
}
}
複製代碼
ViewGroup(GroupLeader.kt)
class GroupLeader : LinearLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet) : super(context,attrs)
constructor(context: Context?, attrs: AttributeSet, defStyleAttr: Int):super(context, attrs, defStyleAttr)
val ROLE_TAG:String = "組長"
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> Log.i(ROLE_TAG,"清楚需求了")
}
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_MOVE -> Log.i(ROLE_TAG,"正在努力工做")
MotionEvent.ACTION_UP -> Log.i(ROLE_TAG,"作好了")
}
return true
}
}
複製代碼
View(Programmer.kt)
class Programmer : View {
val ROLE_TAG:String = "程序員"
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet) : super(context,attrs)
constructor(context: Context?, attrs: AttributeSet, defStyleAttr: Int):super(context, attrs, defStyleAttr)
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> Log.i(ROLE_TAG,"知道要求了,我立刻作")
}
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_MOVE -> Log.i(ROLE_TAG,"正在努力搬磚。。。")
MotionEvent.ACTION_UP -> Log.i(ROLE_TAG,"作好了,累死了")
}
return true
}
}
複製代碼
結果:
這是最經典最基礎的一個流程了吧,一開始還好好的在乎料之中,但有個東西讓我吃了好大的苦頭,卡在這半天了。就是在重寫這三個方法的時候他們的默認返回值都是super.dispatchTouchEvent(ev),在啥都不怎麼修改的時候,最原始的代碼是沒有問題的。(當改成true或false都不行,在有種狀況中,我打印了一下super的返回值,發現是true,但結果卻出乎個人意料,我只是先調用了super並記錄下了它返回的值,在用這個值做爲當前函數的返回值,獲得的事件結果倒是不同的)
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
var mbool:Boolean = super.dispatchTouchEvent(ev)
when (ev?.action) {
MotionEvent.ACTION_DOWN -> Log.i(ROLE_TAG,"有個新需求,分配新需求" + mbool)
}
return mbool
}
複製代碼
變爲了組長未卜先知了,這讓我有點納悶了,而後我直接將函數返回值改成了true和false,再也不調用super,兩個的返回值都同樣,但事件被消費了,沒有往下傳遞了,只有使用super的時候進行了向下的傳遞。(這個問題目前那麼多時間去了解,我就先將super當成一個返回值的狀態吧)
其餘流程是我一個一個更改每一個函數的返回值爲true或false或默認的super試出來的。畫了一個事件分發與攔截的流程圖。