Android View的事件分发机制解析
作者:網易·周龍
最近剛看完android-Ultra-Pull-To-Refresh下拉刷新的源碼,發現在寫自定義控件時,對于View的事件的傳遞總是搞不太清楚,而View事件的分發機制,又是解決可能出現的滑動沖突的基礎,因此專門學習了一下,結合一個例子來好好分析分析。
先上Demo,很簡單,就是一個自定義ViewGroup和一個自定義View。View分發機制中,有三個很重要的方法是:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent,它們分別對應著事件的分發、攔截和消耗,因此我在自定義View的這三個方法里加上Log,根據打印的日志來分析下具體的流程。
public class CustomViewGroup extends LinearLayout { ? ?private static final String TAG = "Mylog"; ? ?public CustomViewGroup(Context context) { ? ? ? ?super(context);} ? ?public CustomViewGroup(Context context, AttributeSet attrs) { ? ? ? ?super(context, attrs);} ? ?@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.d(TAG, "customViewGroup.dispathcTouchEvent "+ev.toString()); ? ? ? ?return super.dispatchTouchEvent(ev);} ? ?@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.d(TAG, "customViewGroup.onInterceptTouchEvent "+ev.toString()); ? ? ? ?return super.onInterceptTouchEvent(ev);// ? ? ? ?return ?true;} ? ?@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.d(TAG, "customViewGroup.onTouchEvent "+event.toString()); ? ? ? ?return super.onTouchEvent(event);// ? ? ? ?return ?true;} ? ?@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { ? ? ? ?super.onLayout(changed, l, t, r, b);} }public class CustomView extends View { ? ?private static final String TAG = "Mylog"; ? ?public CustomView(Context context) { ? ? ? ?super(context);} ? ?public CustomView(Context context, AttributeSet attrs) { ? ? ? ?super(context, attrs);} ? ?public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { ? ? ? ?super(context, attrs, defStyleAttr);} ? ?@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Log.d(TAG, "customview.dispatchtouchevent "+event.toString()); ? ? ? ?return super.dispatchTouchEvent(event);} ? ?@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.d(TAG, "customview.onTouchEvent "+event.toString());// ? ? ? ?return ?true;return super.onTouchEvent(event);} }customView.setOnTouchListener(new View.OnTouchListener() { ? ? ? ? ? ?@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.d(TAG, "customView.onTouch "+event.toString()); ? ? ? ? ? ? ? ?return false;}});customViewGroup.setOnTouchListener(new View.OnTouchListener() { ? ? ? ? ? ?@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.d(TAG, "customViewGroup.onTouch "+event.toString()); ? ? ? ? ? ? ? ?return false;}});} ? ?@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.d(TAG, "Activity.dispatchTouchEvent "+ev.toString()); ? ? ? ?return super.dispatchTouchEvent(ev);} ? ?@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.d(TAG, "Activity.onTouchEvent "+event.toString()); ? ? ? ?return super.onTouchEvent(event);}在Activity界面,我對CustomView和CustomViewGroup設定了onTouch監聽。
下圖是界面的一個示意圖,Activity的界面包含了一個ViewGroup,而ViewGroup里包含了一個View,最簡單的線性布局。
事件傳遞的基本原則:對于一個根ViewGroup來說,點擊事件產生后,首先會傳遞給自身,并調用其dispatchTouchEvent方法,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接著調用onTouchEvent來處理這個事件;如果這個ViewGroup不攔截,則將事件傳遞給子元素,子元素調用其onInterceptTouchEvent方法,如此反復直到最終被處理。
同一事件序列:指手指接觸屏幕起,到手指離開屏幕的那一刻,在這個過程中產生的一系列動作。一般以down事件開始,一系列move事件,并以一個up事件結束。
可以表示為:[down, move,move,move......up]
在上述的基本原則下,我們來根據例子分析下View的事件分發過程。
在默認情況下,點擊區域3 View,Activity消耗事件
打印出來的Log如上,事件ACTION_DOWN先從Activity的dispatchTouchEvent開始傳遞,向下分發,由于Activity和ViewGroup默認都是不攔截事件的,因此不會攔截,一直傳遞到最內側的View,注意到View的onTouch方法比其onTouchEvent方法先調用,這表示onTouch具有更高的優先級。由于我默認設置view也不處理事件,因此事件最終回到了Activity的onTouchEvent中,并接受了ACTION_UP事件。這表明一旦一個View接受了ACTION_DOWN事件,那么接下來的同一序列的事件都將交給它處理。結合下圖可以更好的來理解。
內部View消耗ACTION_DOWN事件
將代碼中,CustomView的onTouchEvent返回值為true,則表示可以對接受到的事件進行消耗。我們點擊區域3,并滑動一段距離,此時打印log如下:
可以看到,當View的onTouchEvent返回true時,當ACTION_DOWN事件分發到View后,就不在會向父層轉發,表示此View將會對事件經行處理,之后的ACTION_MOVE等同一序列的事件,都將沿著之前的路徑轉發給此View。
外部ViewGroup消耗ACTION_DOWN事件
將CustomView的onTouchEvent返回改成super.onTouchEvent,并將CustomViewGroup中的onTouchEvent和onInterceptTouchEvent返回均設為true,表示將由ViewGroup來攔截事件,并進行處理。我們點擊內部的區域3 View:得到的打印logo如下:
從log中,我們可以發現,一旦ViewGroup準備攔截事件,則事件將不再被轉發給View,因此View的dispatchTouchEvent等函數均不會被觸發,另外,在之后的同一序列事件中,ViewGroup的onInterceptTouchEvent函數也不會被觸發,這表示:一旦某個ViewGroup決定攔截事件,則系統不再會詢問接下來的事件ViewGroup是否要攔截。
點擊區域2 ViewGroup
這種情況比較簡單,此時因為點擊的區域中,不包含其他View,因此ViewGroup將不會把事件轉發給ViewGroup中的其他子View。而在Activity和ViewGroup中正常的進行事件分發,這里給出打印的logo,流程圖示意省略。
事件經過View后,在由ViewGroup處理
這種情況相對少見一點。我們需要將ViewGroup的onInterceptTouchEvent設為super,onTouchEvent設為true,View的onTouchEvent設為super. 這保證了事件將會被傳遞給子View,但子View不會去消耗這個事件。同樣點擊區域3 View后,logo如下:
和預期的一致,事件正常的分發到了View當中,但是子View并沒有處理事件,而是交回到ViewGroup來處理。值得注意的是,之后同一序列的其他事件,都不在會分發給View,而是直接交給ViewGroup的onTouch方法去處理。這意味著一個View一旦接收了事件的轉發而不處理,那么系統將不會把同一序列的其他事件轉發給它。
大概情況就這么幾種,常見的一般是直接由ViewGroup來消耗或者View消耗事件。
這里主要還是從應用層表面來分析了一些View事件的分發過程,如果想深入了解各個函數的實現,可以去閱讀一下View的源碼,從而更好的證實上述的結果。另外,注意到,View(不是ViewGroup)是不包含onInterceptTouchEvent方法的,這表示只要有事件傳遞給View,其onTouchEvent方法就會被觸發,而ViewGroup是默認不攔截任何事件的,其onInterceptTouchEvent方法默認返回false。
在了解了View的事件分發機制后,就可以對一些出現滑動沖突的情況進行自己的處理,之后的文章中會繼續寫寫學習心得。
· END ·
網易云信∣真正穩定的IM云服務
ID:neteaseim ?長按關注,精彩不斷
總結
以上是生活随笔為你收集整理的Android View的事件分发机制解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 易创课堂武汉站-NTES@百位创业者智慧
- 下一篇: 云信“欢乐颂活动”中奖名单