[TOC]

## 事件分發如何通過遞歸實現(dispatchTouchEvent)
遞歸分為遞過程和歸過程。

當 UI 事件來臨時,它首先被分發到 activity,activity 分發給 window,window 再分發給 view 樹。分發過程 **通過 dispatchTouchEvent 函數完成。它的返回值是 Boolean,如果為 true,表示事件被消費;為 false,表示事件未被消費。** dispatchTouchEvent 在不同的分發階段有不同的作用
對于 View 而言,它沒有子 View,**View 自己的 dispatchTouchEvent 實際上會執行到 onTouchEvent** 來進行事件的消費

對于 ViewGroup 而言,它重寫了 dispatchTouchEvent, 先將事件分發給自己的子 View。
如果子 View 可以消費事件, dispatchTouchEvent 直接返回 true;如果子 View 都不消費事件,就調用自己的 onTouchEvent 來進行事件的消費。**(ViewGroup 的 onTouchEvent 實際上就是 super.dispatchTouchEvent)**

這樣,Activity 通過調用 Window#superDispatchTouchEvent 方法將事件分發給 Window,Window 調用 DecorView#dispatchTouchEvent 將事件分發給 decorView。DecorView 是一個 ViewGroup,它又將事件分發給子 ViewGroup 和子 View。當有一個 View 消費掉事件時,他就會向上返回,通過遞歸鏈返回到 Activity#dispatchTouchEvent。最終完成事件的分發,消費和返回。
## 如何避免每一個事件的分發都需要遞歸嗎?
ViewGroup 里面用了一個成員變量 mFirstTouchTarget 來保存消費事件的子 View 信息,因為安卓是支持多指操作的,所以這個 mFirstTouchTarget 是一個 TouchTarget 的鏈表。在View 的 dispatchTouchEvent 可以分為三個階段:判斷是否需要攔截; 分發事件找到消費事件的子 View,更新到 mFirstTouchTarget;根據是否攔截和 mFirstTouchTarget 再次分發事件
所以 Android 為了避免每個事件都遞歸遍歷,定義了一個 **【事件序列】** 的概念:將用戶每一次觸摸屏幕 --> 移動屏幕-->抬起手指稱為一個事件序列。
一個事件序列必然包含 ACTION\_DOWN,ACTION\_MOVE,ACTION\_UP 等多個事件。其中 ACTION\_MOVE 數量不確定,ACTION\_DOWN 和 ACTION\_UP 數量則為 1
當接收到 ACTION\_DOMN 事件時,意味著一次完成事件序列的開始。ViewGroup 會通過遞歸遍歷找到 View 樹中真正對事件進行消費的子 View,并將其保存。這之后接收到 ACTION\_MOVE 和 ACTION\_DOWN 事件時,則跳過遞歸遍歷的過程,直接交給之前保存的消費者。當下一次 ACTION\_DOWN 事件來臨時重置整個過程

## 如何在事件被分發前攔截
> 嗯,了解的還挺仔細的嘛。那如果有一個 view 可以消費事件,但我想在事件分發給它之前進行攔截,該怎么做?
ViewGroup 提供了一個攔截事件的函數**onInterceptTouchEvent,返回值為 Boolean。** 表示是否攔截事件。如果攔截,則會調用 onTouchEvent 進行事件的進一步處理。

## 如何處理滑動沖突
當父 View 和子 View 都有機會消費事件,但消費的時機不符合業務的需要(比如需要子 View 消費事件但父 View 先消費了),就會產生滑動沖突問題
解決辦法一般分為**內部攔截法**和**外部攔截法**。
**內部攔截法**就是通過重寫底層 View 的 dispatchTouchEvent 和 onTouchEvent 方法來決定是否消費事件。
**外部攔截法**就是重寫 ViewGroup 的 dispatchTouchEvent 和 onInterceptTouchEvent 方法決定是否把事件分發給 View。
兩種方法實際上就是對分發事件的 View 和被分發事件的 View 做不同的邏輯判斷。
## 講講ACTION\_CANCEL事件?
> 好的,看來平時沒少解決滑動沖突的問題哈。剛才你提到了事件序列對吧,你說說 ACTION\_CANCEL 事件是用來干嘛的?
前面我們提到在一個事件序列中,如果有 View 能夠消費事件,那么該事件序列所有的后續事件都會交給這個 View 處理。但如果不希望它處理全部的后續事件怎么辦?比如手指點擊一個 Button 然后滑出它的邊界。在這個事件序列中,我只希望 Button 處理它邊界內的 move 事件。對于邊界外的 move 事件,雖然它們都在一個事件序列中,但理論上不應該再傳遞給 Button 了。
ACTION\_CANCEL 就是用來解決這個問題的。當 Button 判斷 move 事件已經超出 view 的邊界時,會將自己的 mPrivateFlags 置為 cancel 狀態。等下次事件分發來臨,Button 的父 ViewGroup 會檢測 Button 的 mPrivateFlags,如果為 cancel 則將之前保存的 mFirstTouchTarget(也就是指向 Button 的引用) 設為 null,并向 Button 發送一個 ACTION\_CANCEL 事件,表示以后不會再接受事件了。

# 參考資料
[【面試官爸爸】嘮嘮Android事件分發?](https://juejin.cn/post/6984432840880422949)
[必問的事件分發,你答得上來嗎](https://juejin.cn/post/6844903823824158733)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺