Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 事件攔截機制分析

事件攔截機制分析

編輯:關於Android編程

當Android系統捕獲到用戶的各種輸入事件後,如何准確地傳遞給真正需要這個事件的控件呢?Android給我們提供了一整套完善的事件傳遞、處理機制,來幫助開發者完成准確的事件分配與處理。
  要了解觸摸事件的攔截機制,首先要了解什麼是觸摸事件?顧名思義,觸摸事件就是捕獲觸摸屏幕後產生的事件。當點擊一個按鈕時,通常就會產生兩個或者三個事件——按鈕按下,這是事件一;如果不小心滑動一點,這就是事件二;當手抬起,這是事件三。Android為觸摸事件封裝了一個類——MotionEvent,如果重寫onTouchEvent()方法,那就會發現給方法的參數就是這樣一個MotionEvent。其實,只要是重寫觸摸相關的方法,參數一般都含有MotionEvent,可見它的重要性。
  在MotionEvent裡面封裝了不少好東西,比如觸摸點的坐標,可以通過event.getX()方法和event.getRawX()方法取出坐標點;再比如獲得點擊的事件類型,可以通過不同的Action(如MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE)來進行區分,並實現不同的邏輯。
  如此看來,觸摸事件還是比較簡單的,其實就是一個動作類型加坐標而已。但是我們知道,Android的View結構是樹形結構,也就是說,View可以放在ViewGroup裡面,通過不同的組合來實現不同的樣式。那麼問題來了,View放在一個ViewGroup裡面,這個ViewGroup又放在另一個ViewGroup裡面,甚至還有可能繼續嵌套,一層層地疊起來。可我們的觸摸事件就一個,到底該分給誰呢?同一個事件,子View和父ViewGroup都有可能想要進行處理。因此,這就產生了“事件攔截”這個“霸氣”的稱呼。
  當然,事件攔截可以很復雜,也可以很簡單。但是初學者卻經常“卡”在這裡不知道如何繼續進行,所以我們不想通過過多的源代碼讓大家不知所措。我們通過最直觀的Log信息,讓大家先有一個大概的了解,知道事件攔截的本質,然後大家在結合源代碼學習時,就可以有方向、有目的性去理解了。
  首先,請想象一下生活中非常常見的場景:假設你所在的公司,有一個總經理,級別最高;他下面有一個部長,級別次之;最低層,就是干活的你,沒有級別。現在董事會交給總經理一項任務,總經理將這項任務布置給了部長,部長又把任務安排給了你。而當你好不容易干完活了,你就把任務交給部長,部長覺得任務完成得不錯,於是就簽了他的名字交給總經理,總經理看了也覺得不錯,就業簽了名字交給董事會。這樣,一個任務就順利完成了。如果大家能非常清楚地理解這樣一個場景,那麼對於事件攔截機制,你就超過了40%的開發者了。下面,我們再來超越剩下的開發者。為了能過方便地了解整個事件的流程,我們設計了這樣一個實例,如下圖所示。


事件攔截實例

  一個總經理——MyViewGroupA,最外層的ViewGroup(紅色)。
  一個部長——MyViewGroupB,中間的ViewGroup(綠色)。
  一個干活的你——MyView,在最底層(藍色)。
  本實例的整個布局結構如下圖所示。

實例布局結構

  代碼非常簡單,只是重寫了事件攔截和處理的幾個方法,並給它加上了一些Log而已。
  對於ViewGroup來說,重寫了如下所示的三個方法。

 

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d("blankj", "ViewGroupA dispatchTouchEvent" + ev.getAction());
    return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.d("blankj", "ViewGroupA onInterceptTouchEvent" + ev.getAction());
    return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d("blankj", "ViewGroupA onTouchEvent" + event.getAction());
    return super.onTouchEvent(event);
}

而對於View來說,重寫了如下所示的兩個方法。

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d("blankj", "View onTouchEvent" + event.getAction());
    return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.d("blankj", "View dispatchTouchEvent" + event.getAction());
    return super.dispatchTouchEvent(event);
}

從上面的代碼中可以看到,ViewGroup級別比較高,比View多了一個方法——onInterceptTouchEvent()。這個方法看名字就能猜到是事件攔截的核心方法。我們先不修改任何返回值,只是點擊一下View,然後看Log會怎樣記錄我們的操作和程序響應。點擊View後的Log如下所示。

D/blankj: ViewGroupA dispatchTouchEvent0
D/blankj: ViewGroupA onInterceptTouchEvent0
D/blankj: ViewGroupB dispatchTouchEvent0
D/blankj: ViewGroupB onInterceptTouchEvent0
D/blankj: View dispatchTouchEvent0
D/blankj: View onTouchEvent0
D/blankj: ViewGroupB onTouchEvent0
D/blankj: ViewGroupA onTouchEvent0

可以看見,正常情況下,時間的傳遞順序是:
  總經理(MyViewGroupA)→部長(MyViewGroupB)→你(View)。事件傳遞的時候,先執行dispatchTouchEvent()方法,再執行onInterceptTouchEvent()方法。
  事件的處理順序是:
  你(View)→部長(MyViewGroupB)→總經理(MyViewGroupA)。事件處理都是執行onTouchEvent()方法。
  事件傳遞的返回值非常容易理解:true,攔截,不繼續;false,不攔截,繼續流程。
  事件處理的返回值也類似:true,處理了,不用審核了;false,給上級處理。
  初始情況下,返回值都是false。
  這裡為了能夠方便大家理解事件攔截的過程,在事件傳遞中,我們只關心onInterceptTouchEvent(),而dispatchTouchEvent()方法雖然是事件分發的第一步,但一般情況下,我們不太會去改寫這個方法,所以暫時不管這個方法。可以把上面的整個事件過程整理成如下圖所示的一張圖。


事件處理過程

  相信大家只要把MyView想成自己,就能充分理解事件分發、攔截、處理的整個流程了。
  下面我們稍微改動一下,假設總經理(MyViewGroupA)發現這個任務太簡單了,覺得自己完成就可以了,完全沒必要再找下屬。因此時間就被總經理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件給攔截了,即讓MyViewGroupA的onInterceptTouchEvent()方法返回true,我們再來看一下Log。

 

D/blankj: ViewGroupA dispatchTouchEvent0
D/blankj: ViewGroupA onInterceptTouchEvent0
D/blankj: ViewGroupA onTouchEvent0

跟我們設想的一樣,總經理(MyViewGroupA)把所有事情都干了,沒後面人的事了。同理,我們讓部長(MyViewGroupB)也來當一次好人,即讓部長(MyViewGroupB)使用onInterceptTouchEvent()方法返回true,把事件攔截下來,Log就會使以下這樣。

D/blankj: ViewGroupA dispatchTouchEvent0
D/blankj: ViewGroupA onInterceptTouchEvent0
D/blankj: ViewGroupB dispatchTouchEvent0
D/blankj: ViewGroupB onInterceptTouchEvent0
D/blankj: ViewGroupB onTouchEvent0
D/blankj: ViewGroupA onTouchEvent0

可以看到,這次部長(MyViewGroupB)當了好人,你(MyView)就不用干活了。
  那麼這兩種情況,也可以整理成類似如上圖所示的圖。
  總經理(MyViewGroupA)攔截事件,如下圖所示。


事件攔截A

  部長(MyViewGroupB)攔截事件,如下圖所示。

事件攔截BvcTjKE15VmlldynV4r7NveHK+MHLo6zS8s6qxOO3tbvYdHJ1ZaOsse3KvrK708PP8snPvLa747Gowcuho9XiyrGjrM7Sw8fNrNH5wLTV+8Dtz8K52M+1zbyjrMjnz8LL+cq+oaM8L3A+DQo8Y2VudGVyPg0KCTxiciAvPg0KCTxpbWcgYWx0PQ=="事件處理A" src="/uploadfile/Collfiles/20160722/201607220946321335.png" title="\" />

  你(MyView)終於翻身做了主,決定了自己的命運。但是,如果部長(MyViewGroupB)看到了你的報告,覺得太丟人,不敢給經理看,所以他就偷偷地返回true,整個事件也就到此為止了,即部長(MyViewGroupB)將自己的onTouchEvent返回true,Log如下所示。

 

D/blankj: ViewGroupA dispatchTouchEvent0
D/blankj: ViewGroupA onInterceptTouchEvent0
D/blankj: ViewGroupB dispatchTouchEvent0
D/blankj: ViewGroupB onInterceptTouchEvent0
D/blankj: View dispatchTouchEvent0
D/blankj: View onTouchEvent0
D/blankj: ViewGroupB onTouchEvent0

他們之間的關系圖如下圖所示。


事件處理B

  通過對前面幾種情況的分析,相信大家能比較容易地了解事件的分發、攔截、處理事件的流程了。在後面的學習中,結合源碼,你會更加深入地理解,為什麼流程會是這樣的?初學者在學習的時候,最好先對流程有一個大致的認識之後,再去接觸源碼,這樣就不會一頭霧水,摸不著頭腦,從而喪失學習的興趣。
項目地址→EventIntercept
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved