(轉載請註明做者:RubiTree,地址:blog.rubitree.com )java
事件分發,我想大部分人都能說幾句,哦,三大方法,哦,那段經典僞代碼,哦,責任鏈... 但若是要讓你完完整整捋一遍,你可能就開始支支吾吾了,只能想到啥說啥git
這塊的東西確實麻煩,說出來不怕嚇到你,事件流到底怎麼流與這些因素都有關係:是什麼事件類型(DOWN/MOVE/UP/CANCEL
)、在哪一個視圖層次(Activity/ViewGroup/View
)、在哪一個回調方法(dispatch()/onIntercept()/onTouch()
)、回調方法給不一樣的返回值(true/false/super.xxx
),甚至對當前事件的不一樣處理還會對同一事件流中接下來的事件形成不一樣影響github
好比我能夠問:重寫某個ViewGroup
裏的dispatchTouchEvent
方法,對MOVE
事件返回false
,整個事件分發過程會是什麼樣的?微信
因而就有人對這些狀況分門別類進行總結,獲得了不少規律,也畫出了紛繁複雜的事件分發流程圖:app
甚至還有相似題圖那樣的動態流程圖 (是的,吸引你進來的題圖竟然是反面教材,我也很心疼啊,畫了我半個下午,結果並無太大的幫助)框架
這些規律和流程圖確實是對的,並且某種意義上也是很是清晰的,能幫助你在調試 Bug 的時候找到一點方向。 你或許能夠奮發圖強,把這些流程圖和規律背下來,也能在須要的時候一通嘰裏呱啦背完你們大眼瞪小眼。 但它們並不能讓你真正理解事件分發是什麼樣子,你可能某一次花費了大量的時間去看懂它們,可是「每次都能看明白!過一段時間又忘了!」 (某段有表明性的評論原話)ide
但講道理,分發個觸摸事件爲何會這麼複雜呢?須要這麼複雜嗎?圖啥呢?佈局
因而,讓咱們回到起點,看看分發觸摸事件究竟是爲了解決一個什麼樣的問題,有沒有更簡單的分發辦法?而後看看當需求增長的時候,要怎麼調整這個簡單的分發策略? 看到最後你就會發現,原來一切是那麼地天然。post
因此,不用死記硬背,也不用急着去懟完整的事件分發流程,那麼多複雜的邏輯和狀況其實都是圍繞着最根本的問題發展出來的,是隨着需求的增長一步步變得複雜的,理解了演化過程,你天然會對其演化的結果瞭然於胸,想忘都忘不掉。測試
從根本問題出發,一切就會變得天然而然。
艾維巴蒂,黑喂狗! 下面我將從最簡單的需求開始思考方案、編寫代碼,而後一步步增長需求、調整方案、繼續編寫代碼,爭取造出一個麻雀雖小五臟俱全的事件分發框架。
咱們先實現一個最簡單的需求:Activity 中有一堆層層嵌套的 View,有且只有最裏邊那個 View 會消費事件
(黃色高亮 View 表明能夠消費事件,藍色 View 表明不消費事件)
思考方案:
Activity
鏈接着根ViewDecorView
,它是通往外界的橋樑,能接收到屏幕硬件發送過來的觸摸事件Activity
開始,通過一層一層 ViewGroup ,傳到最裏邊的 ViewpassEvent(ev)
方法,父親一層層往裏調,能把事件傳遞過去,就完成了需求示意圖
麻雀代碼:
(本文代碼使用Kotlin編寫,核心代碼也提供了Java版本)
open class MView {
open fun passEvent(ev: MotionEvent) {
// do sth
}
}
class MViewGroup(private val child: MView) : MView() {
override fun passEvent(ev: MotionEvent) {
child.passEvent(ev)
}
}
複製代碼
Activity
當成MViewGroup
處理也沒有問題MViewGroup
繼承MView
而不是反過來,由於 MView
是不須要 child
字段的而後咱們增長一條需求,讓狀況複雜一點:Activity
中有一堆層層嵌套的View,有好幾個疊着的View能處理事件
同時須要增長一條設計原則:用戶的一次操做,只能被一個View真正處理(消費)
若是使用第一次試造的框架,要遵照這條原則,就須要在每個能夠處理事件的View層級,判斷出本身要處理事件後,不繼續調用child
的passEvent()
方法了,保證只有本身處理了事件。 但若是真這樣實現了,在大部分場景下會顯得怪怪的,由於處理事件的順序不對:
因此實現新增需求的一個關鍵是:找到那個適合處理事件的View,而咱們經過對業務場景進行分析,獲得答案是:那個最裏面的View適合處理事件
這就不能是等parent
不處理事件了才把事件傳給child
,應該反過來,你須要事件的處理順序是從裏向外:裏邊的child
不要事件了,才調用parent
的passEvent()
方法把事件傳出來。 因而得加一條向外的通道,只能在這條向外的通道上處理事件,前面向裏的通道什麼都不幹,只管把事件往裏傳。 因此這時你有了兩條通道,改個名字吧,向裏傳遞事件是passIn()
方法,向外傳遞並處理事件是passOut()
方法。
示意圖
麻雀代碼:
open class MView {
var parent: MView? = null
open fun passIn(ev: MotionEvent) {
passOut(ev)
}
open fun passOut(ev: MotionEvent) {
parent?.passOut(ev)
}
}
class MViewGroup(private val child: MView) : MView() {
init {
child.parent = this // 示意寫法
}
override fun passIn(ev: MotionEvent) {
child.passIn(ev)
}
}
複製代碼
這段代碼沒有問題,很是簡單,可是它對需求意圖的表達不夠清晰,增長了框架的使用難度
passIn()
的時候只傳遞事件,但願在passOut()
的時候每一個View決定是否要處理事件,並進行處理,並且在處理事件後,再也不調用parent
的passOut()
方法把事件傳出來因而咱們用一個叫作dispatch()
的方法單獨放事件傳遞的控制邏輯,用一個叫作onTouch()
的方法做爲事件處理的鉤子,並且鉤子有一個返回值,表示鉤子中是否處理了事件:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
override fun dispatch(ev: MotionEvent): Boolean {
var handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
return handled
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
這樣寫完,整個行爲其實沒有變化,但你會發現:
dispatch()
中,一目瞭然onTouch()
單純是一個鉤子,框架使用者只須要關心這個鉤子和它的返回值,不用太關心控制流程parent
也不須要了上文的實現看上去已經初具雛形了,但其實連開始提的那條原則都沒實現完,由於原則要求一次操做只能有一個 View 進行處理,而咱們實現的是一個觸摸事件只能有一個View進行處理。 這裏就涉及到一次觸摸操做和一個觸摸事件的區別:
DOWN/UP/ING
,其中ING
有點不夠專業,改個名字叫MOVE
吧DOWN
事件開始、中間是多個MOVE
事件、最後結束於UP
事件的事件流組成因而設計原則更確切地說就是:一次觸摸產生的事件流,只能被一個View消費
在上次試造的基礎上把一個事件變成一個組事件流,其實很是簡單:處理DOWN
事件時跟前面處理一個事件時同樣,但須要同時記住DOWN
事件的消費對象,後續的MOVE/UP
事件直接交給它就好了
麻雀代碼:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) handled = onTouch(ev)
} else {
if (isChildNeedEvent) handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
代碼好像增長了不少,其實只多作了兩件事:
isChildNeedEvent
狀態,對是子View是否處理了DOWN
事件進行記錄,並在其餘觸摸事件時使用這個狀態DOWN
事件的最開始和收到UP
事件的最後,重置狀態此時框架使用者仍是隻須要關心onTouch()
鉤子,在須要處理事件時進行處理並返回true
,其餘事情框架都作好了。
上面的框架已經能完成基本的事件分發工做了,但下面這個需求,你嘗試一下用如今框架能實現嗎? 需求:在可滑動View中有一個可點擊View,須要讓用戶即便按下的位置是可點擊View,再進行滑動時,也能夠滑動外面的的可滑動View。
假如使用上面的框架:
因此直接使用如今的模型去實現的「條目可點擊的滑動列表」會永遠滑動不了。
那怎麼辦呢?
dispatch()
方法在傳入事件過程當中的人設,讓它不是隻能往裏傳遞事件,而是在本身能消費事件的時候把事件給本身
直接想實現以爲處處是矛盾,找不到突破口,那就從頭開始吧,從什麼樣的觸摸反饋是用戶以爲天然的出發,看看這種符合直覺的反饋方案是否存在,找出來它是什麼,再考慮咱們要怎麼實現:
DOWN
事件剛來的時候,能判斷用戶想幹什麼嗎?很抱歉,不能DOWN
事件傳過來的時候,判斷出用戶到底想作什麼,因而兩個View其實都不能肯定本身是否要消費事件我*,這不傻*了嗎,還搞什麼GUI啊,你們都用命令行吧 等等,不要着急,GUI仍是得搞的,不搞沒飯吃的我跟你講,因此你仍是得想一想,想盡辦法去實現。
你先忘記前面說的原則,你想一想,不考慮其餘因素,也不是隻能用DOWN
事件,只要你能判斷用戶的想法就行,你有什麼辦法
DOWN
,而後MOVE
很小一段,也不會MOVE出這個子View,關鍵是比較短的時間就UP
DOWN
,而後開始MOVE
,這時候可能會MOVE出這個子View,也可能不,但關鍵是比較長的時間也沒有在UP
,一直是在MOVE
DOWN
不行,還得看接下來的事件流,得走着瞧UP
,就是點擊裏邊的ViewUP
,但沒怎麼MOVE
,就是長按裏邊的ViewMOVE
比較長的距離,就是滑動外面的View看上去這個目標 View 斷定方案很不錯,安排得明明白白,但咱們現有的事件處理框架實現不了這樣的斷定方案,至少存在如下兩個衝突:
DOWN
的時候判斷當前事件流是否是該給本身,因此一開始它們都只能返回false
。但爲了能對後續事件作判斷,你但願事件繼續流過它們,按照當前框架的邏輯,你又不能返回false
。因此要解決上述的衝突,就確定要對上一版的事件處理框架進行修改,並且看上去一不當心就會大改
dispatch()
方法在傳入事件過程當中的人設,讓它不是隻傳遞事件了,還能夠在往裏傳遞事件前進行攔截,可以看狀況攔截下事件並交給本身的 onTouch()
處理onTouch()
裏對DOWN
事件返回true
,無論是否識別出當前屬於本身的消費模式disptach
事件了,而是直接給本身的onTouch()
onIntercept()
在父 View 往裏dispatch
事件前,開發者能夠覆寫這個方法,加入本身的事件模式分析代碼,而且能夠在肯定要攔截的時候進行攔截
onTouch()
示意圖:
因而使用思路二,在「三造」的基礎上,修改獲得如下代碼:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
寫的過程當中增長了一些對細節的處理:
DOWN
事件的dispatch()
前須要攔截,在後續事件中,也須要加入攔截,不然沒法實現中途攔截的目標isSelfNeedEvent
記錄本身是否攔截過事件,若是攔截過,後續事件直接就交給本身處理這一下代碼是否是看上去瞬間複雜了,但其實只是增長了一個事件攔截機制,對比上一次試造的輪子,會更容易理解。(要是 Markdown 支持代碼塊內自定義着色就行了)
並且對於框架的使用者來講,關注點仍是很是少
onIntercept()
方法,判斷何時須要攔截事件,須要攔截時返回true
onTouch()
方法,若是處理了事件,返回true
上面的處理思路雖然實現了需求,但可能會致使一個問題:裏邊的子 View 接收了一半的事件,可能都已經開始處理並作了一些事情,父 View 突然就不把後續事件給它了,會不會違背用戶操做的直覺?甚至出現更奇怪的現象?
這個問題確實比較麻煩,分兩類狀況討論
pressed
狀態,若是你設置了對應的background
,你的 View 就會有高亮效果MOVE
事件了,這會有問題,就這個按下高亮的例子,若是你只是不傳MOVE
事件了,那誰來告訴裏邊的子View取消高亮呢?因此你須要在中斷的時候也傳一個結束事件
UP
事件嗎?也是不行的,由於這樣就匹配了裏邊點擊的模式了,會直接觸發一個點擊事件,這顯然不是咱們想要的CANCEL
onTouch()
的同時,另外生成一個新的事件發給本身的子View,事件類型是CANCEL
,它將是子View收到的最後一個事件ViewPager
裏有三個page,page裏是ScrollView
,ViewPager
能夠橫向滑動,page裏的ScrollView
能夠豎向滑動
ViewPager
把事件給裏邊ScrollView
以後,它也會偷偷觀察,若是你一直是豎向滑動,那沒話說,ViewPager
不會觸發攔截事件ViewPager
就會開始緊張,想「組織終於決定是我了嗎?真的假的,那我可就不客氣了」,因而在你斜滑必定距離以後,突然發現,你劃不動ScrollView
了,而ViewPager
開始動ScrollView
的豎滑被取消了,ViewPager
把事件攔下來,開始橫滑ScrollView
裏有一些按鈕,按鈕有長按事件,長按再拖動就能夠移動按鈕
ScrollView
把事件攔下來呢?dispatch
方法返回一個特別的值給外邊(以前只是true
和false
,如今要加一個)requestDisallowInterceptTouchEvent()
,子View調用它改變父View的一個狀態,同時父View每次在準備攔截前都會判斷這個狀態(固然這個狀態只對當前事件流有效)因此,連同上一次試造,總結一下
DOWN
事件時就肯定,只能在後續的事件流中進一步判斷CANCEL
事件給兒子就完了另外有幾個值得一提的地方:
onTouch()
後,onTouch()
只會收到後半部分的事件,這樣會不會有問題呢?
在「四造」的基礎上,修改獲得如下代碼:
interface ViewParent {
fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean)
}
open class MView {
var parent: ViewParent? = null
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
open class MViewGroup(private val child: MView) : MView(), ViewParent {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
private var isDisallowIntercept = false
init {
child.parent = this
}
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
// add cancel
val cancel = MotionEvent.obtain(ev)
cancel.action = MotionEvent.ACTION_CANCEL
handled = child.dispatch(cancel)
cancel.recycle()
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
isDisallowIntercept = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
override fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean) {
this.isDisallowIntercept = isDisallowIntercept
parent?.requestDisallowInterceptTouchEvent(isDisallowIntercept)
}
}
複製代碼
此次改動主要是增長了發出CANCEL
事件和requestDisallowInterceptTouchEvent
機制
CANCEL
事件時有一個細節:沒有在給 child
分發CANCEL
事件的同時繼續把原事件分發給本身的onTouch
2. 這是源碼中的寫法,不是我故意的,多是爲了讓一個事件也只能有一個View處理,避免出現bugrequestDisallowInterceptTouchEvent
機制時,增長了ViewParent
接口
雖然目前整個框架的代碼有點複雜,但對於使用者來講,依然很是簡單,只是在上一版框架的基礎上增長了:
requestDisallowInterceptTouchEvent()
方法onTouch
方法中對事件消費而且作了一些操做,須要注意在收到CANCEL
事件時,對操做進行取消到這裏,事件分發的主要邏輯已經講清楚了,不過還差一段 Activity 中的處理,其實它作的事情相似ViewGroup,只有這幾個區別:
onTouch()
因此很少講了,直接補上Activity的麻雀:
open class MActivity(private val childGroup: MViewGroup) {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
open fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = childGroup.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
handled = childGroup.dispatch(ev)
}
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
到這裏,咱們終於造好了一個粗糙但不劣質的輪子,源碼的主要邏輯與它的區別不大,具體區別大概有:TouchTarget
機制、多點觸控機制、NestedScrolling 機制、處理各類 listener、結合View的狀態進行處理等,相比主要邏輯,它們就沒有那麼重要了,你們能夠自行閱讀源碼,以後有空也會寫關於多點觸控和TouchTarget
的內容 (挖坑預警)
輪子的完整代碼能夠在在這裏查看(Java版本) 這個輪子把源碼中與事件分發相關的內容剝離了出來,能看到:
但輪子不是最重要的,最重要的是整個演化的過程。
因此回頭看,你會發現事件分發其實很簡單,它的關鍵不在於「不一樣的事件類型、不一樣的View種類、不一樣的回調方法、方法不一樣的返回值」對事件分發是怎麼影響的。 關鍵在於 「它要實現什麼功能?對實現效果有什麼要求?使用了什麼解決方案?」,從這個角度,就能清晰並且簡單地把事件分發整個流程梳理清楚。
事件分發要實現的功能是:對用戶的觸摸操做進行反饋,使之符合用戶的直覺。
從用戶的直覺出發能獲得這麼兩個要求:
第二個要求是最難實現的,若是有多個View均可以消費觸摸事件,怎麼斷定哪一個View更適合消費,而且把事件交給它。 咱們使用了一套簡單但有效的先到先得策略,讓內外的可消費事件的View擁有近乎平等的競爭消費者的資格:它們都能接收到事件,並在本身斷定應該消費事件的時候去發起競爭申請,申請成功後事件就所有由它消費。
(轉載請註明做者:RubiTree,地址:blog.rubitree.com )
可能有人會問,聽你紙上談兵了半天,你講的真的跟源碼同樣嗎,這要是不對我不是虧大了。 問的好,因此接下來我會使用一個測試事件分發的日誌測試框架對這個小麻雀進行簡單的測試,還會有實踐部分真刀真槍地把上面講過的東西練起來。
測試的思路是經過在每一個事件分發的鉤子中打印日誌來跟蹤事件分發的過程。 因而就須要在不一樣的 View 層級的不一樣鉤子中,針對不一樣的觸摸事件進行不一樣的操做,以製造各類事件分發的場景。
爲了減小重複代碼簡單搭建了一個測試框架(全部代碼都能在此處查看),包括一個能夠代理 View 中這些的操做的接口IDispatchDelegate
及其實現類,和一個DispatchConfig
統一進行不一樣的場景的配置。 以後建立了使用統一配置和代理操做的 真實控件們SystemViews
和 咱們本身實現的麻雀控件們SparrowViews
。
在DispatchConfig
中配置好事件分發的策略後,直接啓動SystemViews
中的DelegatedActivity
,進行觸摸,使用關鍵字TouchDojo
過濾,就能獲得事件分發的跟蹤日誌。 同時,運行SparrowActivityTest
中的dispatch()
測試方法,也能獲得麻雀控件的事件分發跟蹤日誌。
先配置策略,模擬View
和ViewGroup
都不消費事件的場景:
fun getActivityDispatchDelegate(layer: String = "Activity"): IDispatchDelegate {
return DispatchDelegate(layer)
}
fun getViewGroupDispatchDelegate(layer: String = "ViewGroup"): IDispatchDelegate {
return DispatchDelegate(layer)
}
fun getViewDispatchDelegate(layer: String = "View"): IDispatchDelegate {
return DispatchDelegate(layer)
}
複製代碼
能看到打印的事件分發跟蹤日誌:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept_BE |type:down
|layer:SViewGroup |on:Intercept_AF |result(super):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch_BE |type:down
|layer:SView |on:Touch_AF |result(super):false |type:down
|layer:SView |on:Dispatch_AF |result(super):false |type:down
|layer:SViewGroup |on:Touch_BE |type:down
|layer:SViewGroup |on:Touch_AF |result(super):false |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:down
|layer:SActivity |on:Touch_BE |type:down
|layer:SActivity |on:Touch_AF |result(super):false |type:down
|layer:SActivity |on:Dispatch_AF |result(super):false |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
...
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SActivity |on:Touch_BE |type:up
|layer:SActivity |on:Touch_AF |result(super):false |type:up
|layer:SActivity |on:Dispatch_AF |result(super):false |type:up
複製代碼
BE
表明 before
,表示該方法開始處理事件的時候,用AF
表明after
,表示該方法結束處理事件的時候,而且打印處理的結果View
和ViewGroup
都不消費DOWN
事件時,後續事件將再也不傳遞給View
和ViewGroup
再配置策略,模擬View
和ViewGroup
都消費事件,同時ViewGroup
在第二個MOVE
事件時認爲本身須要攔截事件的場景:
fun getActivityDispatchDelegate(layer: String = "Activity"): IDispatchDelegate {
return DispatchDelegate(layer)
}
fun getViewGroupDispatchDelegate(layer: String = "ViewGroup"): IDispatchDelegate {
return DispatchDelegate(
layer,
ALL_SUPER,
// 表示 onInterceptTouchEvent 方法中,DOWN 事件返回 false,第一個 MOVE 事件返回 false,第二個第三個 MOVE 事件返回 true
EventsReturnStrategy(T_FALSE, arrayOf(T_FALSE, T_TRUE, T_TRUE), T_SUPER),
ALL_TRUE
)
}
fun getViewDispatchDelegate(layer: String = "View"): IDispatchDelegate {
return DispatchDelegate(layer, ALL_SUPER, ALL_SUPER, ALL_TRUE)
}
複製代碼
能看到打印的事件分發跟蹤日誌:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept |result(false):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch |result(true):true |type:down
|layer:SView |on:Dispatch_AF |result(super):true |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:down
|layer:SActivity |on:Dispatch_AF |result(super):true |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(false):false |type:move
|layer:SView |on:Dispatch_BE |type:move
|layer:SView |on:Touch |result(true):true |type:move
|layer:SView |on:Dispatch_AF |result(super):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(true):true |type:move
|layer:SView |on:Dispatch_BE |type:cancel
|layer:SView |on:Touch_BE |type:cancel
|layer:SView |on:Touch_AF |result(super):false |type:cancel
|layer:SView |on:Dispatch_AF |result(super):false |type:cancel
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Touch |result(true):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Touch |result(true):true |type:up
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:up
|layer:SActivity |on:Dispatch_AF |result(super):true |type:up
複製代碼
ViewGroup
攔截事件先後,事件是如何分發的除了以上場景外,我也模擬了其餘複雜的場景,能看到系統控件和麻雀控件打印的日誌如出一轍,這就說明了麻雀控件中的事件分發邏輯,確實與系統源碼是一致的。
並且從打印的日誌中,能清晰地看到事件分發的軌跡,對理解事件分發過程也有很大的幫助。因此你們若是有須要,也能夠直接使用這個框架像這樣對觸摸事件分發的各類狀況進行調試。
實際上進行事件分發的實踐時,會包括兩方面內容:
GestureDetector
,它用起來很是方便時間關係,這部分暫時直接去看另外一篇透鏡《看穿 > NestedScrolling 機制》吧,它提供了過得去的實踐場景。
(以爲對你有幫助的話,不妨點個贊再走呀~ 給做者一點繼續寫下去的動力)
4.1.事件分發經典僞代碼
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
複製代碼