編輯:關於android開發
Android事件傳遞機制也是Android系統中比較重要的一塊,事件類型有很多種,這裡主要討論TouchEvent的事件在framework層的傳遞處理機制。因為對於App開發人員來說,理解framework層的事件傳遞機制,就差不多了。
當Android設備的屏幕,接收到觸摸的動作時,屏幕驅動把壓力信號(包括壓力大小,壓力位置等)傳遞給系統底層,然後操作系統經過一系列的處理,然後把觸摸事件一層一層的向上傳遞,最終事件會被准確的傳遞到產生事件的對象上,系統會遍歷每一個View對象,然後計算觸摸點在哪一個View中。比如A和B兩個View,是兄弟View,AView產生的觸摸事件,是不會被分發到B上面的。
在Android系統中,一個單獨的事件基本上是沒什麼作用的,只有一個事件序列,才有意義。一個事件序列正常情況下,定義為 DOWN、MOVE(0或者多個)、UP/CANCEL。事件序列以DOWN事件開始,中間會有0或者多個MOVE事件,最後以UP事件或者CANCEL事件結束。
DOWN事件作為序列的開始,有一個很重要的職責,就是尋找事件序列的接受者,怎麼理解呢?framework 在DOWN事件的傳遞過程中,需要根據View事件處理方法(onTouchEvent)的返回值來確定事件序列的接受者。如果一個View的onTouchEvent事件,在處理DOWN事件的時候返回true,說明它願意接受並處理該事件序列。
觸摸事件到了framework層之後,首先會被傳遞到Activity,然後Activity會把事件委托給它內部的Window對象進行分發處理,而Window對象又會委托它內部的DecorView進行事件分發處理。我們都知道,DecorView是整棵View樹的根節點,所以整個事件傳遞過程的復雜度就是事件在View樹種分發傳遞的復雜度。 Android View框架提供了3個對事件的主要操作概念。
1、事件的分發機制,dispatchTouchEvent。主要是parent根據觸摸事件的產生位置,以及child是否願意負責處理該系列事件等狀態,向其child分發事件的機制。
2、事件的攔截機制,onInterceptTouchEvent。主要是parent根據它內部的狀態、或者child的狀態,來把事件攔截下來,阻止其進一步傳遞到child的機制。
3、事件的處理機制,onTouchEvent。主要是事件序列的接受者(可以是一個View或者ViewGroup),對事件作出處理,並且向其parent傳遞處理結果的機制。
在Java中,傳遞計算結果,有很多種途徑,這裡采用的是一種適用於同步調用的方法,返回值的方法。每個機制都使用boolean類型作為其返回值,那麼每個機制的每個返回值是什麼含義呢。
1、事件的分發機制,dispatchTouchEvent。
true-事件被以該節點為根節點的View樹成功處理,此時該事件就算是處理完成了,事件不會再向上返還給View的父節點(把事件分發過來的那個節點)。
false-以該節點為根節點的View樹種,沒有一個View(包括該View)成功處理了此事件,所以事件會向上返還給View的父節點(把事件分發過來的那個節點)。
2、事件的攔截機制,onInterceptTouchEvent。主要是parent根據它內部的狀態、或者child的狀態,來把事件攔截下來,阻止其進一步傳遞到child的機制。
true-當前ViewGroup(因為View中沒有該方法,而沒有child的VIew也不需要有攔截機制)希望該事件不再傳遞給其child,而是希望自己處理。
false-當前ViewGroup不准備攔截該事件,事件正常向下分發給其child。
3、事件的處理機制,onTouchEvent。主要是事件序列的接受者(可以是一個View或者ViewGroup),對事件作出處理,並且向其parent傳遞處理結果的機制。
true-表示該View成功處理了該事件,該處理結果會向上通知給其parent。
false-表示該View沒有成功處理該事件,那麼它的parent會有機會來處理該事件(parent標記為事件序列接受者,parent 的 onTouchEvent 在 Down 事件時返回true)。
源代碼基於SDK 23
/**把事件分發到目標對象,因為這裡是View對象,默認不含有child,所以這裡他會把事件分發給自己 */
public boolean dispatchTouchEvent(MotionEvent event);
源代碼:
不給出,有興趣的讀者執行查閱SDK
偽代碼:
public boolean dispatchTouchEvent(MotionEvent event){ boolean result = false; //如果有事件監聽器,先讓監聽器處理事件。 if (mOnTouchListener.onTouch(event)) { //如果監聽器成功處理了該事件,處理結果設置為true。 result = true; } //如果沒有監聽器,就調用自身的onTouchEvent方法來處理事件。 if (!resutlt && onTouchEvent(event)) { //如果自身的onTouchEvent成功處理事件,處理結果設置為true。 result = true; } return result; }
/**默認實現是返回false,也就是默認不攔截任何事件 */
public boolean onInterceptTouchEvent(MotionEvent ev);
/**根據內部攔截狀態,向其child或者自己分發事件 */
public boolean dispatchTouchEvent(MotionEvent ev);
源代碼:
不給出,有興趣的讀者執行查閱SDK
偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ACTION_DOWN事件 || 沒有事件處理對象) { if (允許攔截事件,該標志位由child調用requestDisallowInterceptTouchEvent設置) { //查詢攔截機制的結果,根據該結果來判斷是否需要攔截 intercepted = onInterceptTouchEvent(ev); } else { //不允許攔截,那麼不攔截 intercepted = false; } } else { //不是DOWN,並且有處理對象,允許攔截,中斷事件傳遞 intercepted = true; } if (不取消 && 不攔截) { if (ACTION_DOWN) { //找尋接收事件序列的對象 for (遍歷所有childView) { if (觸摸點不在childView內部) { continue; } if (childView.dispatchTouchEvent(event)) { 保存處理該事件的View,後續事件直接傳遞到該View,不要重新計算; } } } if (還沒有事件處理對象) { //當前View樹中沒找到合適的child處理對象,把事件給自己處理,View.dispatchTouchEvent()就是把事件分發給自己 super.dispatchTouchEvent(event); } else { //傳遞給child childView.dispatchTouchEvent(event); } } else if (攔截) { //攔截事件,把事件給自己處理,View.dispatchTouchEvent()就是把事件分發給自己 super.dispatchTouchEvent(event); } return 處理結果; }
/**干澀parent的事件分發機制,通知parent,是否攔截後續事件,如果設置為true,parent就不會攔截該事件,不管什麼狀態。設置為false,parent走正常的攔截流程 */
publicvoidrequestDisallowInterceptTouchEvent(booleandisallowIntercept);
源代碼:
不給出,有興趣的讀者執行查閱SDK
偽代碼:
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (已經是當前要設置的狀態) { // 已經處於這個狀態, 假設我們的parent也是這個狀態 return; } 設置該狀態; // 傳遞給parent if (有父容器) { 設置父容器的攔截狀態; } }
我們都知道,如果ScrollView內部嵌套ListView,那麼ListView是不可以滑動的,效果如下圖所示:
那麼其實這就是典型的事件沖突問題,就是說,原本應該被ListView用來上下滑動的事件,被ScrollView攔截了。就導致ListView不能正常滑動。
我們來看一下ScrollView的源代碼:
onInterceptTouchEvent的偽代碼:
public boolean onInterceptTouchEvent(MotionEvent ev) { /* * 這個方法決定了我們是否要攔截事件. * 如果返回true, onTouchEvent會被調用並且我們開始做實際的Scroll操作. */ /* * 大部分循環的狀態: 用戶在再拖拽的狀態並且正在移動手指, * 我們希望攔截這個事件 */ final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } //其他操作 ...................... }
所以正常的上下拖拽,ScrollView都會攔截。
那麼我們下面改進一下,就是當我們滑動ScrollView中非ListView的區域時,ScrollView滑動,而我們滑動ListView的時候,ListView滑動,效果看起來如下圖所示:
這裡解決方法如下:
既然ScrollView會攔截事件,那麼當我們滑動ListView的時候,我們不希望ScrollView攔截事件,這裡我們繼承ListView,在onTouchEvent中,請求ScrollView不要攔截事件。
部分代碼如下:
@Override public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: getParent().requestDisallowInterceptTouchEvent(false); break; default: break; } return true; }
這樣就可以很好的解決事件沖突的問題。
還有一種方法就是覆寫parent的onInterceptTouchEvent方法,來修改事件攔截的狀態。
注冊界面設計及實現之(三)SharedPerferences實現數據暫存,sharedptr實現開發步驟: 創建一個SharedPerferences接口對象,並使用其
上次講的Android上的SQLite分頁讀取,只用文本框顯示數據而已,這次就講得更加深入些,實現
界面優化處理技術之(二)編輯文本框組件優化處理,文本框組件開發步驟: 在res下drawable下創建xml文件 添加標簽設置顯示效果 1 <?xml ver
《Android源碼設計模式解析與實戰》讀書筆記(二十四) 第二十四章、橋接模式 橋接模式也稱為橋梁模式,是結構型設計模式之一。橋接模式中體現了“單一職責原