Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 事件分發詳解及示例代碼

Android 事件分發詳解及示例代碼

編輯:關於Android編程

事件分發是Android中非常重要的機制,是用戶與界面交互的基礎。這篇文章將通過示例打印出的Log,繪制出事件分發的流程圖,讓大家更容易的去理解Android的事件分發機制。

一、必要的基礎知識

1、相關方法

Android中與事件分發相關的方法主要包括dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三個方法,而事件分發一般會經過三種容器,分別為Activity、ViewGroup、View。下表對這三種容器分別擁有的事件分發相關方法進行了整理。

事件相關方法 方法功能 Activity ViewGroup View public boolean dispatchTouchEvent 事件分發 Yes Yes Yes public boolean onInterceptTouchEvent 事件攔截 No Yes No public boolean onTouchEvent 事件消費 Yes Yes Yes

分發: dispatchTouchEvent如果返回true,則表示在當前View或者其子View(子子…View)中,找到了處理事件的View;反之,則表示沒有尋找到

攔截: onInterceptTouchEvent如果返回true,則表示這個事件由當前View進行處理,不管處理結果如何,都不會再向子View傳遞這個事件;反之,則表示當前View不主動處理這個事件,除非他的子View返回的事件分發結果為false

消費: onTouchEvent如果返回true,則表示當前View就是事件傳遞的終點;反之,則表示當前View不是事件傳遞的終點

2、相關事件

這篇文章中我們只考慮4種觸摸事件: ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANAL。 事件序列:一個事件序列是指從手指觸摸屏幕開始,到手指離開屏幕結束,這個過程中產生的一系列事件。一個事件序列以ACTION_DOWN事件開始,中間可能經過若干個MOVE,以ACTION_UP事件結束。 接下來我們將使用之前的文章自定義View——彈性滑動中例子來作為本文的示例,簡單增加一些代碼即可,修改之後的代碼請點擊查看。

二、示例的默認情況

我們可以從示例代碼的xml中看出,圖片都是可點擊的。

<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="10dp"
  tools:context="com.idtk.customscroll.MainActivity"
  >

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/zhiqinchun"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/hanzhan"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/shengui"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/dayu"
    android:clickable="true"/>

</com.idtk.customscroll.ParentView>

我們現在來點擊一下,查看下打印出的日志。

 

根據打印出的log來繪制一張事件傳遞的流程圖

 

現在來理一下事件序列的流程:

  1. ACTION_DOWN事件從Activity#dispatchTouchEvent方法開始
  2. ACTION_DOWN事件傳遞至ViewGroup#dispatchTouchEvent方法,ViewGroup#onInterceptTouchEvent返回false,表示不攔截ACTION_DOWN
  3. ACTION_DOWN事件傳遞到View#dispatchTouchEvent方法,在View#onTouchEvent進行執行,返回true,表示事件已經被消費
  4. 返回的結果true,被回傳到View#dispatchTouchEvent,之後回傳到ACTION_DOWN事件的起點Activity#dispatchTouchEvent方法
  5. ACTION_UP事件的傳遞過程與ACTION_DOWN相同,這裡不再復述

這裡使用工作中的情況來模擬一下:老板(Activity)、項目經理(ViewGroup)、軟件工程師(View)

老板分配一個任務給項目經理(Activity#dispatchTouchEvent → ViewGroup#dispatchTouchEvent),項目經理選擇自己不做這個任務(ViewGroup#dispatchTouchEvent返回false),交由軟件工程師處理這個任務(<View#dispatchTouchEvent)(我們忽略總監與組長的情況),軟件工程師完成了這個任務(View#onTouchEvent返回true)

把結果告訴項目經理(返回結果true,View#dispatchTouchEvent→ ViewGroup#dispatchTouchEvent),項目經理把結果告訴老板(返回結果true,ViewGroup#dispatchTouchEvent→Activity#dispatchTouchEvent)

項目經理完成的不錯,老板決定把這個項目的二期、三期等都交給項目經理,同樣項目經理也覺得這個軟件工程師完成的不錯,所以也把二期、三期等都交給這個工程師來做
通過上面的傳遞過程,我們可以得出一些結論:

  1. 某個ViewGroup如果onInterceptTouchEvent返回為false,則表示ViewGroup不攔截事件,而是將其傳遞給View#dispatchTouchEvent方法
  2. 事件總是由父元素分發給子元素
  3. 某個View如果onTouchEvent返回true,表示事件被消費,則其結果將直接通過dispatchTouchEvent方法傳遞回Activity
  4. 如果某個View消費了ACTION_DOWN事件,那麼這個事件序列中的後續事件也將交由其進行處理(有一些特殊情況除外,比如在序列中的之後事件進行攔截)

三、在View中不消費事件

我們現在修改示例代碼的xml部分,android:clickable="true"全部修改為android:clickable="false"

<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="10dp"
  tools:context="com.idtk.customscroll.MainActivity"
  >

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/zhiqinchun"
    android:clickable="false"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/hanzhan"
    android:clickable="false"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/shengui"
    android:clickable="false"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/dayu"
    android:clickable="false"/>

</com.idtk.customscroll.ParentView>

這時再點擊一下,查看新打印出的日志

 

現在根據log中顯示的邏輯,分別繪制ACTION_DOWN事件與ACTION_UP事件傳遞的流程圖

 

 

我們來整理下這個事件序列的流程:

  1. ACTION_DOWN事件的傳遞與之前相同,不同的地方在於,返回值的傳遞
  2. 因為不可點擊,View#onTouchEvent返回值為false,將其傳遞給自己的dispatchTouchEvent方法,之後傳遞到ViewGroup#dispatchTouchEvent方法,再傳遞到ViewGroup#onTouchEvent方法
  3. ViewGroup返回false之後,ACTION_DOWN事件交由Activity#onTouchEvent方法進行處理,然而依舊返回false,最後ACTION_DOWN事件的返回結果即為false
  4. ACTION_UP事件在發現View、ViewGroup並不處理ACTION_DOWN事件後,直接將其傳遞給了Activity#onTouch方法處理,處理返回false,ACTION_UP事件的返回結果即為false

這裡使用工作中的情況來模擬:依舊是老板(Activity)、項目經理(ViewGroup)、軟件工程師(View) 從老板交任務給項目經理,項目經理交任務給工程師,這一段流程和之前的例子相同。不同之處是軟件工程師沒有完成這個任務(View#onTouchEvent返回false),告訴項目經理我沒有完成,然後項目經理自己進行了嘗試,同樣沒有完成(ViewGroup#onTouchEvent返回false),項目經理告訴了老板,我沒有完成,然後老板自己試了下也沒有完成這個任務(Activity#onTouchEvent返回false),但之後的也有項目的二期、三期,不過老板知道你們完成不了,所以都是他自己進行嘗試,不過很慘都沒完成。(這段有點與正常情況不同,不過只是打個比方)

通過結合上面兩個例子,可以得出一些結論:

  1. 某個View如果onTouchEvent返回false,表示事件沒有被消費,則事件將傳遞給其父View的onTouchEvent進行處理
  2. 某個View如果它不消耗ACTION_DOWN事件,那麼這個序列的後續事件也不會再交由它來處理
  3. 如果事件沒有View對其進行處理,那麼最後將有Activity進行處理
  4. View默認的onTouchEvent在View可點擊的情況下,將會消耗事件,返回true;不可點擊的情況下,則不消耗事件,返回false(longClickable的情況,讀者可以自行測試,結果與clickable相同)

四、在ViewGroup中攔截事件

事件分發中攔截的情況,這裡我把它分為2種,一種是在ACTION_DOWN事件時,就進行攔截的;另一種是在ACTION_DOWN之後的事件序列中,對事件進行了攔截。

1、在事件開始時攔截

為了達到在ViewGroup中,一開始就攔截觸摸事件的效果,我們需要進行修改,在ParentView#onInterceptTouchEvent方法的最後部分,我注釋掉的intercept=true;進行恢復,然後為activity_main.xml中的ParentView增加android:clickable="true"屬性。

<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="10dp"
  tools:context="com.idtk.customscroll.MainActivity"
  >

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/zhiqinchun"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/hanzhan"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/shengui"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/dayu"
    android:clickable="true"/>

</com.idtk.customscroll.ParentView>

 

我們現在來看下攔截情況下的事件流程圖

 

 

這裡大部分和之前的例子相同,主要的區別是在於ViewGroup#onInterceptTouchEvent方法中,對傳遞的事件進行了攔截,返回true,ACTION_DOWN事件就傳遞到了ViewGroup#onTouchEvent中進行處理,ACTION_DOWN事件之後的傳遞就與之前的例子相同了。另一點重要的區別是,在ViewGroup攔截下事件之後,此事件序列的其余事件,在進入ViewGroup#dispatchTouchEvent方法之後,不在需要進行是否攔截事件的判斷,而是直接進入了onTouchEvent方法之中。

使用工作中的情況來模擬:老板(Activity)、項目經理(ViewGroup)、軟件工程師(View) 老板吧任務交給項目經理,項目經理認為這個項目比較難,所以決定自己處理(ViewGroup#onInterceptTouchEvent,return true),項目經理比較厲害他把任務完成了(ViewGroup#onTouchEvent,return true),然後他告訴老板他完成了(return true,ViewGroup#dispatchTouchEvent→Activity#dispatchTouchEvent)。之後老板依舊會把任務交給項目經理,項目經理知道這個任務難度,所以不假思索(也就是這個事件序列中的其余事件沒有經過ViewGroup#onInterceptTouchEvent)的自己來做。

通過上面的例子,可以得出一些結論:

某個ViewGroup如果onInterceptTouchEvent返回為true,則ViewGroup攔截事件,將事件傳遞給其onTouchEvent方法進行處理

某個ViewGroup如果它的onInterceptTouchEvent返回為true,那麼這個事件序列中的後續事件,不會在進行onInterceptTouchEvent的判斷,而是由它的dispatchTouchEvent方法直接傳遞給onTouchEvent方法進行處理

2、在事件序列中攔截

這裡把使用的示例恢復到初始狀態,然後把我在ParentView#onInterceptTouchEvent方法,switch內的兩個注釋掉的intercept = true;代碼進行恢復,最後部分intercept = true;再次注釋掉。

<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="10dp"
  tools:context="com.idtk.customscroll.MainActivity"
  >

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/zhiqinchun"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/hanzhan"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/shengui"
    android:clickable="true"/>

  <com.idtk.customscroll.ChildView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/dayu"
    android:clickable="true"/>

</com.idtk.customscroll.ParentView>

重新運行之後,滑動一個圖片,來看看Log

 

這裡分成兩張圖片,是因為中間有很多ACTION_MOVE,為了方便觀察,所以只截取了Log的首尾部分。 這裡的關鍵部分,就是紅框中的ACTION_CANCEL,可以看到ACTION_DOWN事件的傳遞時onInterceptTouchEvent並沒有攔截,返回false,在其後的事件ACTION_MOVE再次進入onInterceptTouchEvent時,ViewGroup對事件進行了攔截,這樣將會對View傳遞一個ACTION_CANCEL事件,之後的ACTION_MOVE事件就不再傳遞給View了。

使用工作中的情況來模擬:老板(Activity)、項目經理(ViewGroup)、軟件工程師(View) 這裡的情況就是,一期的任務和第一個例子一樣的情況一樣,由軟件工程師完成,不過忽然項目經理覺得二期的任務有點難,然後決定自己完成。這時就給工程師說,這個項目的後續任務,不要你來完成了(ACTION_CANCEL)。

從這裡也可以得出一個結論:

某個View接收了ACTION_DOWN之後,這個序列的後續事件中,如果在某一刻被父View攔截了,則這個字View會收到一個ACTION_CANCEL事件,並且也不會再收到這個事件序列中的後續事件。

五、小結

本文通過示例打印出的各種Log對Android的事件分發機制進行,得出如下結論。

  1. 一個事件序列是指從手指觸摸屏幕開始,到手指離開屏幕結束,這個過程中產生的一系列事件。一個事件序列以ACTION_DOWN事件開始,中間可能經過若干個MOVE,以ACTION_UP事件結束。
  2. 事件的傳遞過程是由外向內的,即事件總是由父元素分發給子元素
  3. 如果某個View消費了ACTION_DOWN事件,那麼通常情況下,這個事件序列中的後續事件也將交由其進行處理,但可以通過調用其父View的onInterceptTouchEvent方法,對後續事件進行攔截
  4. 如果某個View它不消耗ACTION_DOWN事件,那麼這個序列的後續事件也不會再交由它來處理
  5. 如果事件沒有View對其進行處理,那麼最後將有Activity進行處理
  6. 如果事件傳遞的結果為true,回傳的結果直接通過不斷調用父View#dispatchTouchEvent方法,傳遞給Activity;如果事件傳遞的結果為false,回傳的結果不斷調用父View#onTouchEvent方法,獲取返回結果。
  7. View默認的onTouchEvent在View可點擊的情況下,將會消耗事件,返回true;不可點擊的情況下,則不消耗事件,返回false(longClickable的情況,讀者可以自行測試,結果與clickable相同)
  8. 如果某個ViewGroup的onInterceptTouchEvent返回為true,那麼這個事件序列中的後續事件,不會在進行onInterceptTouchEvent的判斷,而是由它的dispatchTouchEvent方法直接傳遞給onTouchEvent方法進行處理
  9. 如果某個View接收了ACTION_DOWN之後,這個序列的後續事件中,在某一刻被父View攔截了,則這個字View會收到一個ACTION_CANCEL事件,並且也不會再收到這個事件序列中的後續事件

  事件相關方法 方法功能 Activity ViewGroup View public boolean dispatchTouchEvent 事件分發 Yes Yes Yes public boolean onInterceptTouchEvent 事件攔截 No Yes No public boolean onTouchEvent 事件消費 Yes Yes Yes

以上就是對Android 事件分發的資料整理,後續繼續補充相關資料,謝謝大家對本站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved