通俗理解Android中View的事件分發機制及滑動衝突處理
說起Android滑動衝突,是個很常見的場景,比如SliddingMenu與ListView的巢狀,要解決滑動衝突,不得不提及到View的事件分發機制。
一、Touch事件傳遞規則分析
首先,我們要知道Touch事件是包裝在MotionEvent物件中的,在手指與螢幕接觸過程中產生一系列事件,典型的事件有以下三種:
ACTION_DOWN:手指剛接觸螢幕的瞬間
ACTION_UP:手指剛離開螢幕的瞬間
ACTION_MOVE:手指在螢幕上滑動
如果return true,事件會由當前View的dispatchTouchEvent方法進行消費,同時事件會停止向下傳遞;
如果return false,事件分發分為兩種情況:
如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給Activity的onTouchEvent進行消費;
如果當前 View 獲取的事件來自外層父控制元件,則會將事件返回給父View的onTouchEvent進行消費。
如果return true,則表示攔截該事件,並將事件傳遞給當前View的onTouchEvent方法;
如果return false,則表示不攔截該事件,並將該事件交由子View的dispatchTouchEvent方法進行事件分發,重複上述過程;
如果return true,則表示響應並消費該事件;
如果return fasle,則表示不響應事件,那麼該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的onTouchEvent方法返回true,如果到了最頂層View還是返回false,那麼認為該事件不消耗,則在同一個事件系列中,當前View無法再次接收到事件,該事件會交由Activity的onTouchEvent進行處理;
如果return super.dispatchTouchEvent(ev),則表示不響應事件,結果與return false一樣。
這裡也順便說一下,如果一個View同時監聽了onTouch事件和onClick事件,則在onTouchEvent裡面應該返回false,否則點選事件就無法監聽到。後面會提到這一點。
(1)當一個點選事件產生後,它的傳遞過程遵循的規則如下:Activity->Window->View。頂級View接收到事件之後,就會按相應規則去分發事件。如果一個View的onTouchEvent方法返回false,那麼將會交給父容器的onTouchEvent方法進行處理,逐級往上,如果所有的View都不處理該事件,則交由Activity的onTouchEvent進行處理,就跟工作中遇到了難題,逐級找領導解決一個道理,領導解決不了,再找上一級領導。
(2)正常情況下,一個事件序列只能被一個View攔截且消耗,某個View一旦進行事件攔截,那麼這一個事件序列都只能交由他處理,並且onInterceptTouchEvent也不會被再次呼叫,因此,正常情況下一個事件是不能交給兩個View來處理的,當然,特殊做法就是在View的onTouchEvent,處理完之後再返回false,強行交給其他View處理。
(3)如果某一個View開始處理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),則同一事件序列比如接下來進行ACTION_MOVE,則不會再交給該View處理,就像工作中做一件事情,你要嘛做完,要嘛你就不要做這件事了。
(4)在Android中,ViewGroup預設返回false,即不攔截任何事件。
(5)諸如TextView、ImageView這些不作為容器的View,一旦接受到事件,就呼叫onTouchEvent方法,它們本身沒有onInterceptTouchEvent方法。正常情況下,它們都會消耗事件(返回true),除非它們是不可點選的(clickable和longClickable都為false),那麼就會交由父容器的onTouchEvent處理。
(6)View的enable屬性不影響onTouchEvent的預設返回值,只要它clickable或者longClickable為true,則onTouchEvent就會返回true。
(7)點選事件分發過程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent-->OnClickListener的onClick方法。也就是說,我們平時呼叫的setOnClickListener,優先順序是最低的,所以,onTouchEvent或OnTouchListener的onTouch方法如果返回true,則不響應onClick方法...
二、滑動衝突處理過程分析
滑動衝突的場景常見於滑動巢狀,就是一個頁面中可能有兩個或兩個以上的View同時可以滑動,那麼就可能會導致只有其中的一個View能滑動。一個最簡單的螢幕觸控動作觸發了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE—>...->ACTION_MOVE->ACTION_UP。
滑動衝突場景主要有三種:
(1)一個頁面中同時存在左右滑動和上下滑動。
讓外部的View攔截滑動事件,判斷滑動的特徵,如果水平滑動距離>豎直滑動距離,則為水平滑動,反之為豎直滑動。假設內部View可以水平滑動,外部View可以豎直滑動,那麼在外部View的onInterceptTouchEvent方法判斷,如果觸控事件為水平滑動,則應該放行,也就是返回false,然後交給內部View來處理,那麼內部子View就可以實現水平滑動。當然,還有一種方法就是外部View不攔截,交給內部View處理,如果內部View有需要就自己消耗掉,否則交給上一層,但是這樣違反了事件分發機制,所以需配合requestDisallowInterceptTouchEvent(MotionEvent ev)進行處理,這裡就不細說了,有興趣的童鞋可以研究一下。
(2)同時存在兩個豎直或水平滑動
這個主要還得根據具體的需求分析。最簡單的加入是兩個ScrollView巢狀,一般可以判斷ACTION_DOWN在那個View上,就執行那個View的滑動事件。
(3)就是(1)和(2)同時存在的情況
實際上也得看具體業務需求找到突破點,但是處理方式本質上來說都是差不多的,就是要根據滑動策略,來干擾事件分發機制。
附上一段虛擬碼來理清一下思路:
一、Touch事件傳遞規則分析
首先,我們要知道Touch事件是包裝在MotionEvent物件中的,在手指與螢幕接觸過程中產生一系列事件,典型的事件有以下三種:
ACTION_DOWN:手指剛接觸螢幕的瞬間
ACTION_UP:手指剛離開螢幕的瞬間
ACTION_MOVE:手指在螢幕上滑動
那麼,Android中Touch事件是一個怎樣的傳遞過程呢?
事件分發:public boolean dispatchTouchEvent(MotionEvent ev)
Touch事件發生時Activity的dispatchTouchEvent(MotionEvent ev)方法會將事件傳遞給最外層View的dispatchTouchEvent(MotionEvent ev)方法,該方法對事件進行分發。分發邏輯如下:如果return true,事件會由當前View的dispatchTouchEvent方法進行消費,同時事件會停止向下傳遞;
如果return false,事件分發分為兩種情況:
如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給Activity的onTouchEvent進行消費;
如果當前 View 獲取的事件來自外層父控制元件,則會將事件返回給父View的onTouchEvent進行消費。
如果return super.dispatchTouchEvent(ev),事件會自動的分發給當前View的onInterceptTouchEvent方法。
事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)
上面已經提到,如果在dispatchTouchEvent返回super.dispatchTouchEvent(ev),那麼事件將會傳遞到onInterceptTouchEvent方法,該方法對事件進行攔截。攔截邏輯如下:如果return true,則表示攔截該事件,並將事件傳遞給當前View的onTouchEvent方法;
如果return false,則表示不攔截該事件,並將該事件交由子View的dispatchTouchEvent方法進行事件分發,重複上述過程;
如果return super.onInterceptTouchEvent(ev),預設表示攔截該事件,並將事件傳遞給當前View的onTouchEvent方法,和return true一樣。
事件響應:public boolean onTouchEvent(MotionEvent ev)
上面已經提到,在dispatchTouchEvent(事件分發)返回super.dispatchTouchEvent(ev)並且onInterceptTouchEvent(事件攔截返回true或super.onInterceptTouchEvent(ev)的情況下,那麼事件會傳遞到onTouchEvent方法,該方法對事件進行響應。響應邏輯如下:如果return true,則表示響應並消費該事件;
如果return fasle,則表示不響應事件,那麼該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的onTouchEvent方法返回true,如果到了最頂層View還是返回false,那麼認為該事件不消耗,則在同一個事件系列中,當前View無法再次接收到事件,該事件會交由Activity的onTouchEvent進行處理;
如果return super.dispatchTouchEvent(ev),則表示不響應事件,結果與return false一樣。
這裡也順便說一下,如果一個View同時監聽了onTouch事件和onClick事件,則在onTouchEvent裡面應該返回false,否則點選事件就無法監聽到。後面會提到這一點。
上述三個方法到底有什麼區別與聯絡呢?我們通過一段虛擬碼來表示:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){ // 如果onInterceptTouchEvent返回true
consume = onTouchEvent(ev); // 則交由該View的onTouchEvent方法
} else {
consume = child. dispatchTouchEvent(ev); // 否則交由子View的dispatchTouchEvent事件進行分發
}
return consume; // 如果成功消費該事件,則返回true,然後停止傳遞,否則返回false
}
那麼,接下來就總結一下事件的傳遞的規則。(1)當一個點選事件產生後,它的傳遞過程遵循的規則如下:Activity->Window->View。頂級View接收到事件之後,就會按相應規則去分發事件。如果一個View的onTouchEvent方法返回false,那麼將會交給父容器的onTouchEvent方法進行處理,逐級往上,如果所有的View都不處理該事件,則交由Activity的onTouchEvent進行處理,就跟工作中遇到了難題,逐級找領導解決一個道理,領導解決不了,再找上一級領導。
(2)正常情況下,一個事件序列只能被一個View攔截且消耗,某個View一旦進行事件攔截,那麼這一個事件序列都只能交由他處理,並且onInterceptTouchEvent也不會被再次呼叫,因此,正常情況下一個事件是不能交給兩個View來處理的,當然,特殊做法就是在View的onTouchEvent,處理完之後再返回false,強行交給其他View處理。
(3)如果某一個View開始處理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),則同一事件序列比如接下來進行ACTION_MOVE,則不會再交給該View處理,就像工作中做一件事情,你要嘛做完,要嘛你就不要做這件事了。
(4)在Android中,ViewGroup預設返回false,即不攔截任何事件。
(5)諸如TextView、ImageView這些不作為容器的View,一旦接受到事件,就呼叫onTouchEvent方法,它們本身沒有onInterceptTouchEvent方法。正常情況下,它們都會消耗事件(返回true),除非它們是不可點選的(clickable和longClickable都為false),那麼就會交由父容器的onTouchEvent處理。
(6)View的enable屬性不影響onTouchEvent的預設返回值,只要它clickable或者longClickable為true,則onTouchEvent就會返回true。
(7)點選事件分發過程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent-->OnClickListener的onClick方法。也就是說,我們平時呼叫的setOnClickListener,優先順序是最低的,所以,onTouchEvent或OnTouchListener的onTouch方法如果返回true,則不響應onClick方法...
二、滑動衝突處理過程分析
滑動衝突的場景常見於滑動巢狀,就是一個頁面中可能有兩個或兩個以上的View同時可以滑動,那麼就可能會導致只有其中的一個View能滑動。一個最簡單的螢幕觸控動作觸發了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE—>...->ACTION_MOVE->ACTION_UP。
滑動衝突場景主要有三種:
(1)一個頁面中同時存在左右滑動和上下滑動。
讓外部的View攔截滑動事件,判斷滑動的特徵,如果水平滑動距離>豎直滑動距離,則為水平滑動,反之為豎直滑動。假設內部View可以水平滑動,外部View可以豎直滑動,那麼在外部View的onInterceptTouchEvent方法判斷,如果觸控事件為水平滑動,則應該放行,也就是返回false,然後交給內部View來處理,那麼內部子View就可以實現水平滑動。當然,還有一種方法就是外部View不攔截,交給內部View處理,如果內部View有需要就自己消耗掉,否則交給上一層,但是這樣違反了事件分發機制,所以需配合requestDisallowInterceptTouchEvent(MotionEvent ev)進行處理,這裡就不細說了,有興趣的童鞋可以研究一下。
(2)同時存在兩個豎直或水平滑動
這個主要還得根據具體的需求分析。最簡單的加入是兩個ScrollView巢狀,一般可以判斷ACTION_DOWN在那個View上,就執行那個View的滑動事件。
(3)就是(1)和(2)同時存在的情況
實際上也得看具體業務需求找到突破點,但是處理方式本質上來說都是差不多的,就是要根據滑動策略,來干擾事件分發機制。
附上一段虛擬碼來理清一下思路:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) { // 外部View攔截事件
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x; // 分別記錄上次滑動座標
mLastYIntercept = y;
return intercepted; // 看是否需要傳遞給內部View處理
}
參考:
Android開發藝術探索
相關文章
- Android事件分發機制、滑動衝突解決Android事件
- 學習總結 -- View 事件分發機制和滑動衝突View事件
- 【朝花夕拾】Android自定義View篇之(七)Android事件分發機制(下)解決滑動衝突AndroidView事件
- Android事件傳遞、多點觸控及滑動衝突的處理Android事件
- 10分鐘理解 Android View 事件分發機制AndroidView事件
- Android觸控事件傳遞機制及viewpager巢狀fragment衝突處理Android事件Viewpager巢狀Fragment
- Android View 的事件體系 -- 事件分發機制AndroidView事件
- Android View 滑動衝突解決方式以及原理AndroidView
- Android 事件分發機制的理解Android事件
- 像 QQ 一樣處理滑動衝突
- 《Android藝術開發探索》學習筆記之View的事件體系(滑動衝突)Android筆記View事件
- View事件分發機制View事件
- 【Android原始碼】View的事件分發機制Android原始碼View事件
- 事件分發之View事件處理事件View
- 完全理解android事件分發機制Android事件
- 探索View的事件分發機制View事件
- View的事件分發機制分析View事件
- View事件分發機制分析View事件
- Android事件分發:從原始碼角度分析View事件分發機制Android事件原始碼View
- 每日一問:Android 滑動衝突,你們都是怎樣處理的Android
- 理解事件分發和衝突的實戰技巧事件
- Android事件分發機制簡單理解Android事件
- 《Android開發藝術探索》——View事件分發機制AndroidView事件
- Android 事件分發機制原始碼解析-view層Android事件原始碼View
- View 體系詳解:座標系、滑動、手勢和事件分發機制View事件
- 處理併發衝突
- Android自定義View之事件分發機制總結AndroidView事件
- 基於原始碼分析 Android View 事件分發機制原始碼AndroidView事件
- Android從原始碼角度剖析View事件分發機制Android原始碼View事件
- 淺談Android中的事件分發機制Android事件
- UIPanGestureRecognizer進行檢視滑動並處理手勢衝突UI
- 【朝花夕拾】Android自定義View篇之(五)Android事件分發及傳遞機制AndroidView事件
- Android事件分發機制Android事件
- anisble部署及包衝突處理
- 《Android藝術開發探索》學習筆記之View的事件體系(View的事件分發機制)Android筆記View事件
- 【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)三個重要方法的處理邏輯AndroidView事件
- android View的事件分發AndroidView事件
- Android的MotionEvent事件分發機制Android事件