前言
關于Android事件傳遞是Android中一個重點,同時也是一個難點,而且其源碼實現層級復雜,那么接下來通過 父控件 -> 子控件事件模型來理解 事件傳遞機制的一些知識點
接下來通過 理論知識和實際應用來記錄
Android中事件傳遞機制 - 理論知識
Android中事件傳遞機制 - 滑動沖突
1. 結論
分析之前,可以先記住這3個結論:
- 結論1:事件一定是先到達父控件上;
- 結論2:事件簡單說分為3種:Down、Move、Up事件,結合結論1可知,Down、Move、Up事件均是先到達父控件上;
- 結論3:父控件和父類不是一回事;
2. 事件傳遞機制流程
事件模型:父控件 -> 子控件
關于事件模型主要分為3個概念:dispatchTouchEvent()、oninterceptTouchEvent()、onTouchEvent(),分別是事件分發、事件攔截、事件觸摸;
- 對于單個View來說:沒有onInterceptTouchEvent(),因為它不包含其他View,所以不需要攔截方法;
- 對于ViewGroup來說:3個方法都有;
某一個事件到達View或者ViewGroup,一定會最先調用這個控件的dispatchTouchEvent(),dispatchTouchEvent()就是把這個事件分發下去,具體分發邏輯如下:
分析上圖可知:
用戶觸摸該位置時候,最先調用最外層父控件ViewGroupA的 dispatchTouchEvent(),然后調用 ViewGroupA的onInterceptTouchEvent(),如果返回true,表示攔截事件,此時直接調用 ViewGroupA的 onTouchEvent(),如果返回false,就把事件繼續給父控件 ViewGroupB分發;
首先調用 父控件ViewGroupB的 dispatchTouchEvent(),然后調用 ViewGroupB的 onInterceptTouchEvent(),如果返回true,表示攔截事件,直接調用 ViewGroupB的 onTouchEvent(),如果返回false,表示不攔截,繼續把事件分發給下一級View;
上圖只是寫了兩個父控件 ViewGroupA、ViewGroupB,進行分析事件傳遞機制,如果有多個父控件,其事件傳遞機制是一樣的;
- ViewGroupB 把事件分發給View后,由于View沒有 onInterceptTouchEvent(),所以直接調用 View的 dispatchTouchEvent(),然后調用 onInterceptTouchEvent();
以上就是事件傳遞機制流程;
3. 事件響應方向
接下來我們就來分析下事件的響應方向,我們都知道 onTouchEvent()是有返回值的,要么返回true,要么返回false;
分析上圖可知:
ViewGroupB的 onInterceptTouchEvent返回了true,表示攔截了事件,此時會執行 ViewGroupB的 onTouchEvent,如果返回true,表示消費了此事件,事件到此終止;如果返回false,那么這個事件會回傳給 父控件 ViewGroupA,調用父控件 ViewGroupA的 onTouchEvent,同樣的判斷返回true還是false,如果為true,表示消費此事件,事件到此終止;如果返回false,繼續向 上層父控件傳遞事件,以此類推;
此時可以得出兩個方向:
1>:事件傳遞方向:父控件 -> 子控件;
2>:事件響應方向:子控件 -> 父控件;
4. 細致分析
1>:事件的起點由Down開始,然后產生一系列Move事件,最后以Up事件結束;
2>:如果 Down事件傳遞到了 子View,子View在onTouchEvent中對Down事件返回 false,導致的結果就是事件傳遞到 父控件,然后調用 父控件的 onTouchEvent,就意味著 后續的 Move、Up事件都不能傳遞到 子View上,所以對于 View來講,如果想要處理 滑動事件,即就是Move事件,就一定不能在該 View的 MotionEvent.ActionDown中返回false;
3>:如果 Down事件傳遞到了 父View,會調用父View的 onInerceptTouchEvent,如果返回true,那么就會調用 父View的 onTouchEvent,此時如果 onTouchEvent返回true,表示父View 響應 Down事件。
有一個不一樣的地方就是:事件傳遞到 父View的onTouchEvent,是因為 onInterceptTouchEvent返回true攔截了事件導致的,不是由 子View回傳回來的,這種情況下,當 Move、Up事件傳遞到 父View的時候,不會傳遞給子View,并且不再調用自身的onInterceptTouchEvent
5. 什么時候產生 cancel事件?
一個正常的事件應該是 down - move - up,如果一個View響應了 down 事件,但是其 move或者up事件被攔截,這個時候 該View會產生 cancel事件。
6. 子控件是否可以請求父控件是否攔截的行為?
可以。子控件可以 請求父控件不要攔截事件;
調用 子控件.getParent().requestDisallowInterceptTouchEvent(true),表示 請求父控件不要攔截事件。
上句代碼意思就是:事件首先到達 父控件,然后到達 子控件 ,getParent().requestDisallowInterceptTouchEvent(true);這句代碼是在 父控件的 onInterceptTouchEvent攔截事件之后調用的,其效果不是 父控件對本次事件是否攔截的結果,而是后續事件。
比如 view在Down事件調用 getParent().requestDisallowInterceptTouchEvent(true),那么在后續的 Move、Up事件分發到 父控件中,父控件不會攔截,所以這種情況 getParent().requestDisallowInterceptTouchEvent(true);只會影響后續的 Move、Up事件,而不會影響 Down事件;
7. 示例現象分析
常用場景:比如有一個父控件ViewPager,該ViewPager其中的一個 item控件是 ScrollView,會發生的問題是:當ViewPager滑動到 ScrollView時候,發現不能左右滑動
解決方案
自定義一個 MyViewPager,重寫 onInterceptTouchEvent,然后根據多個 Move事件判斷是左右滑動還是上下滑動,如果是左右滑動,則return true將事件攔截;如果上下滑動,則return false,將事件分發給 ScrollView。
一般不會攔截 Down事件,判斷是否攔截,一般是根據一系列的 Move事件得出具體成立的條件