安卓觸摸事件分發機制分析

1. 前言

🎯 一句話總結:

觸摸事件(TouchEvent)會從 Activity 層開始,按從外到內的方式傳遞給每一個 ViewGroup/View,直到某個 View 消費(consume) 它,事件傳遞就會停止。

📌 事件分發三個關鍵方法

方法名所在類作用說明
dispatchTouchEvent()所有 View/ViewGroup事件分發入口,決定是否繼續向下傳遞
onInterceptTouchEvent()僅 ViewGroup是否攔截事件,阻止傳遞給子 View
onTouchEvent()所有 View/ViewGroup事件的最終處理者(消費者)


DecorView是一個應用窗口的根容器,它本質上是一個FrameLayoutDecorView有唯一一個子View,它是一個垂直LinearLayout,包含兩個子元素,一個是TitleView(ActionBar的容器),另一個是ContentView(窗口內容的容器)。

Activity.dispatchTouchEvent()Window.superDispatchTouchEvent()DecorView.dispatchTouchEvent()ViewGroup.dispatchTouchEvent()- onInterceptTouchEvent() → 是否攔截?↓            ↓攔截自己處理     不攔截繼續往下↓子View.dispatchTouchEvent()View.dispatchTouchEvent()- onTouchListener.onTouch()- onTouchEvent()
  • Activity.dispatchTouchEvent()
    • 觸摸事件從系統層傳入,Activity 先接收。
    • 通常會把事件傳給當前的 DecorView(根 View)。
  1. ViewGroup.dispatchTouchEvent()
    • 嘗試調用 onInterceptTouchEvent() 判斷是否攔截事件。
      • 返回 true:表示當前 ViewGroup 要處理,子 View 不再收到事件。
      • 返回 false:繼續把事件傳給子 View。
  2. View.dispatchTouchEvent()
    如果是 ViewGroup,會重復上面的流程(遞歸)。
    • 如果是普通 View,直接調用 onTouchEvent()
  3. onTouchEvent()
    • 如果返回 true,表示事件被消費(消費后不會再向上傳遞)。
    • 如果返回 false,當前控件不處理,事件會被傳回上層 ViewGroup 的 onTouchEvent()

2. Activity、ViewGroup、View事件分發機制分析

Activity事件分發機制
  • Activity.dispatchTouchEvent(MotionEvent event)

源碼(Activity.java僅關鍵代碼):

public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);
}
  • onUserInteraction():通知用戶交互,和事件分發無關。
  • 調用 getWindow().superDispatchTouchEvent(ev)
    • WindowPhoneWindow
    • PhoneWindow 把事件交給了 DecorView(一個 ViewGroup)處理。
  • 如果 superDispatchTouchEvent(ev) 返回 true,說明事件被下面消費了。
  • 否則調用 Activity.onTouchEvent(ev)(比如點擊空白處)。
ViewGroup 事件分發機制

DecorViewViewGroup,所以它遵循 ViewGroup 的事件分發規則。

  • ViewGroup.dispatchTouchEvent(MotionEvent ev)

源碼(ViewGroup.java僅關鍵代碼):

ViewGroup.dispatchTouchEvent 代碼可大致簡化為下面這個樣子

public boolean dispatchTouchEvent(MotionEvent ev) {// 1. 是否攔截事件boolean intercepted = onInterceptTouchEvent(ev);// 2. 如果沒有攔截,遍歷子 View 分發if (!intercepted) {for (int i = childrenCount - 1; i >= 0; i--) {final View child = children[i];if (child.dispatchTouchEvent(ev)) {return true;}}}// 3. 子 View 沒有消費,自己處理return super.dispatchTouchEvent(ev);
}

詳細代碼可以查看ViewGroup.dispatchTouchEventdispatchTransformedTouchEvent方法

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;// Canceling motions is a special case.  We don't need to perform any transformations// or filtering.  The important part is the action, not the contents.final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}// 省略....return handled;}
  • onInterceptTouchEvent(ev):決定是否攔截事件(默認返回 false)。
  • 如果返回 true,自己處理,不再傳遞給子 View。
  • 如果沒有攔截,會遍歷子 View,調用子 View 的 dispatchTouchEvent(ev)
  • 如果有任何一個子 View 返回了 true,說明消費了事件,整個流程結束。
  • 如果子 View 都沒有消費,最后調用自己的 super.dispatchTouchEvent(ev),即作為普通 View 處理。
View 事件分發機制

View 是最終事件的接收者。

  • View.dispatchTouchEvent(MotionEvent ev)

源碼(View.java僅關鍵代碼):

public boolean dispatchTouchEvent(MotionEvent event) {boolean result = false;// 1. 先判斷是否需要觸發 OnTouchListenerListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}// 2. 如果 OnTouchListener 沒消費,再走 onTouchEventif (!result && onTouchEvent(event)) {result = true;}return result;
}
  • 優先執行 onTouchListener.onTouch()
    • 如果返回 true,表示事件被消費,不繼續往下傳遞。
  • 否則走 onTouchEvent(event)

因為點擊事件是在 onTouchEvent 中的 case MotionEvent.ACTION_UP: 中判斷調用的,具體查看 View.performClickInternal()方法。

這里就是,如果你設置了某個 ViewOnTouchListener 并且在 onTouch 方法中返回 true,那么這個 ViewonClick 方法不會執行的原因。


3. 理解 ViewGroup 的遞歸式事件分發?

核心理解:遞歸式分發

  • 父 ViewGroup 收到事件,先問自己:“要不要攔截?”(onInterceptTouchEvent)**
  • 如果 不攔截,就 找出被點擊的子 View
  • 然后 把事件遞給子 ViewdispatchTouchEvent() 方法
  • 子 View 又可以是一個 ViewGroup(比如 LinearLayout),于是子 View 又重復上面的流程: onInterceptTouchEvent()
  • 再分發給自己的子 View。
  • 就這樣,一層一層遞歸下去,直到遇到一個普通 View(沒有子 View 的 Button、TextView),最后交給 onTouchEvent() 來消費。

打個通俗比喻:

  • 一個 ViewGroup 就像一個"村長",負責分發任務。
  • 它收到任務(MotionEvent)后,會問:
    • 我要自己干?(攔截)
    • 還是派給手下某個小村民?(子 View)
  • 村民又是個小村長(嵌套 ViewGroup)的話,繼續往下派。
  • 最后一個真正干活的是普通農民(Button/TextView)。

補充個知識點:

onInterceptTouchEvent() 只在 “ACTION_DOWN” 開始時有意義!!

  • 因為一旦一個手指 ACTION_DOWN 被攔截了,后續的 ACTION_MOVE / ACTION_UP 事件都跟著這個處理鏈走。
  • 如果 DOWN 沒攔截,后面的 MOVE/UP 也不會隨便切換到攔截。

這叫做 事件的捕獲(capture)機制,Android 保證事件流動的一致性。


4. 最后

Android 事件分發是一層層向下傳,遇到攔截或者消費就停;否則事件會向上傳遞,直到有人消費或者丟棄。

還有一個問題,對于沒了解過事件分發機制的同學來說,對于事件分發:由外到內,事件消費:由內到外 的理解,可能有些困惑,包括我自己,其實就可以簡單理解為 ViewGroup 一個方法把事件分發機制寫完,方法中間就是挨個遍歷子 View 挨個問,你要不要這個事件(由外到內),都不要的話,我就要了(又回到了 由內到外 ),帶著這個理解,去看源碼就很容易理解事件分發機制了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/77427.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77427.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77427.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring MVC 多個攔截器的執行順序

一、流程總覽 該流程圖描述了一個多層攔截器鏈的業務處理流程,核心邏輯為: 前置攔截:通過 predHandler1 和 predHandler2 逐層校驗請求合法性。核心處理:通過校驗后執行核心業務邏輯 handler()。后置處理與清理:按反…

django filter 排除字段

在Django中,當你使用filter查詢集(QuerySet)時,通常你會根據模型的字段來過濾數據。但是,有時你可能想要排除某些特定的字段,而不是過濾這些字段。這里有幾種方法可以實現這一點: 使用exclude方…

ByeCode,AI無代碼開發平臺,拖拽式操作構建應用

ByeCode是什么 ByeCode 是一款先進的 AI 無代碼平臺,旨在幫助企業迅速創建數字名片、網站、小程序、應用程序及內部管理系統,無需繁雜的編碼或開發工作。ByeCode 采用直觀的可視化界面和拖拽式操作,使得非技術用戶能夠輕松上手。同時&#x…

AI日報 - 2025年04月28日

🌟 今日概覽(60秒速覽) ▎🤖 能力進展 | Gemini 2.5 Pro成功挑戰《口袋妖怪紅》8道館;AI推理器具備自我糾錯能力;LLM在游戲、多模態理解、代碼遷移等方面展現新能力。 ▎💼 商業動向 | Google回應DOJ反壟斷案&#xff…

在Java中實現List按自定義順序排序的幾種方案

在Java中實現List按自定義順序排序的幾種方案 在實際開發中&#xff0c;我們經常需要對集合中的對象按照特定字段進行排序。當排序規則不是簡單的字母或數字順序&#xff0c;而是自定義的順序時&#xff0c;我們需要采用特殊的方法。本文將以一個List<Person>按省份特定…

微服務架構在云原生后端的深度融合與實踐路徑

??個人主頁??:一ge科研小菜雞-CSDN博客 ????期待您的關注 ???? 一、引言:后端架構的演變,走向云原生與微服務融合 過去十余年,后端架構經歷了從單體應用(Monolithic)、垂直切分(Modularization)、到微服務(Microservices)的演進,每一次變化都是為了解決…

Python中的Walrus運算符分析

Python中的Walrus運算符&#xff08;:&#xff09;是Python 3.8引入的一個新特性&#xff0c;允許在表達式中同時賦值和返回值。它的核心作用是減少重復計算&#xff0c;提升代碼簡潔性。以下是其適用的典型場景及示例&#xff1a; 1. 在循環中避免重復計算 當循環條件需要多次…

用Node.js施展文檔比對魔法:輕松實現Word文檔差異比較小工具,實現Word差異高亮標注(附完整實戰代碼)

引言&#xff1a;當「找不同」遇上程序員的智慧 你是否經歷過這樣的場景&#xff1f; 法務同事發來合同第8版修改版&#xff0c;卻說不清改了哪里 導師在論文修改稿里標注了十幾處調整&#xff0c;需要逐一核對 團隊協作文檔頻繁更新&#xff0c;版本差異讓人眼花繚亂 傳統…

前端瀏覽器窗口交互完全指南:從基礎操作到高級控制

瀏覽器窗口交互是前端開發中構建復雜Web應用的核心能力&#xff0c;本文深入探討23種關鍵交互技術&#xff0c;涵蓋從傳統API到最新的W3C提案&#xff0c;助您掌握跨窗口、跨標簽頁的完整控制方案。 一、基礎窗口操作體系 1.1 窗口創建與控制 // 新窗口創建&#xff08;現代瀏…

Git和Gitlab的部署和操作

一。GIT的基本操作 1.GIT的操作和查看內容 [rootmaster ~]# yum install git -y [rootmaster ~]# git config --list&#xff1a;查看所有配置 2.GIT倉庫初始化 [rootmaster ~]# mkdir /gittest&#xff1a;創建目錄 [rootmaster ~]# cd /gittest/&#xff1a;進入目錄 [rootm…

Linux中線程池的簡單實現 -- 線程安全的日志模塊,策略模式,線程池的封裝設計,單例模式,餓漢式單例模式,懶漢式單例模式

目錄 1. 對線程池的理解 1.1 基本概念 1.2 工作原理 1.3 線程池的優點 2. 日志與策略模式 2.1 日志認識 2.2 策略模式 2.2.1 策略模式的概念 2.2.2 工作原理 2.2 自定義日志系統的實現 3. 線程池設計 3.1 簡單線程池的設計 3.2 線程安全的單例模式線程池的設計 3…

量子力學:量子通信

量子通信是利用量子力學原理對信息進行編碼、傳輸和處理的新型通信方式&#xff0c;以下是其詳細介紹及業界發展現狀&#xff1a; 基本原理 量子疊加態 &#xff1a;量子系統可以處于多個狀態的疊加&#xff0c;如光子的偏振方向可以同時處于水平和垂直方向的疊加態&#xff…

企業架構之旅(1):TOGAF 基礎入門

大家好&#xff0c;我是沛哥兒。今天我們簡單聊下TOGAF哈。 文章目錄 一、TOGAF 是什么定義與核心定位發展歷程與行業地位與其他架構框架的區別 二、TOGAF 核心價值企業數字化轉型助力業務與 IT 的協同作用降本增效與風險管控 三、TOGAF 基礎術語解析架構域&#xff08;業務、…

CSS 內容超出顯示省略號

CSS 內容超出顯示省略號 文章目錄 CSS 內容超出顯示省略號**1. 單行文本省略&#xff08;常用&#xff09;****2. 多行文本省略&#xff08;如 2 行&#xff09;****3. 對非塊級元素生效****完整示例****注意事項** 在 CSS 中實現內容超出顯示省略號&#xff0c;主要通過控制文…

路由器重分發(OSPF+RIP),RIP充當翻譯官,OSPF充當翻譯官

路由器重分發&#xff08;OSPFRIP&#xff09; 版本 1 RIP充當翻譯官 OSPF路由器只會OSPF語言&#xff1b;RIP路由器充當翻譯官就要會OSPF語言和RIP語言&#xff1b;則在RIP中還需要將OSPF翻譯成RIPOSPF 把RIP路由器當成翻譯官&#xff0c;OSPF路由器就只需要宣告自己的ip&am…

AlexNet網絡搭建

AlexNet網絡模型搭建 環境準備 首先在某個盤符下創建一個文件夾&#xff0c;就叫AlexNet吧&#xff0c;用來存放源代碼。 然后新建一個python文件&#xff0c;就叫plot.py吧&#xff0c;往里面寫入以下代碼&#xff0c;用于下載數據集&#xff1a; # FashionMNIST里面包含了…

【計算機網絡】網絡基礎概念

&#x1f4da; 博主的專欄 &#x1f427; Linux | &#x1f5a5;? C | &#x1f4ca; 數據結構 | &#x1f4a1;C 算法 | &#x1f152; C 語言 | &#x1f310; 計算機網絡 這是博主計算機網絡的第一篇文章&#xff0c;本文由于是基礎概念了解&#xff0c;引用了大…

在Spring Boot項目中實現Word轉PDF并預覽

在Spring Boot項目中實現Word轉PDF并進行前端網頁預覽&#xff0c;你可以使用Apache POI來讀取Word文件&#xff0c;iText或Apache PDFBox來生成PDF文件&#xff0c;然后通過Spring Boot控制器提供文件下載或預覽鏈接。以下是一個示例實現步驟和代碼&#xff1a; 1. 添加依賴 …

圖解 Redis 事務 ACID特性 |源碼解析|EXEC、WATCH、QUEUE

寫在前面 Redis 通過 MULTI、EXEC、WATCH 等命令來實現事務功能。Redis的事務是將多個命令請求打包&#xff0c;然后一次性、按照順序的執行多個命令的機制&#xff0c;并且在事務執行期間&#xff0c;服務器不會中斷事務而該去執行其他客戶端的命令請求。 就像下面這樣&#…

LeetCode --- 446 周賽

題目列表 3522. 執行指令后的得分 3523. 非遞減數組的最大長度 3524. 求出數組的 X 值 I 3525. 求出數組的 X 值 II 一、執行指令后的得分 照著題目要求進行模擬即可&#xff0c;代碼如下 // C class Solution { public:long long calculateScore(vector<string>&…