Android之事件分发机制
本文主要包括以下內容
首先來看兩張圖
在執行touch事件時
首先執行dispatchTouchEvent方法,執行事件分發。
再執行onInterceptTouchEvent方法,判斷是否中斷事件,返回true時中斷,執行自己的onTouchEvnet方法.
最后執行onTouchEvent方法,處理事件
View的事件分發
不管是DOWN,MOVE,UP都會按照下面的順序執行:
1、dispatchTouchEvent
2、 setOnTouchListener的onTouch
3、onTouchEvent
其中
如果我們設置了setOnTouchListener,并且return true,那么View自己的onTouchEvent就不會被執行了
總結
1、整個View的事件轉發流程是:
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中會進行OnTouchListener的判斷,如果OnTouchListener不為null且返回true,則表示事件被消費,onTouchEvent不會被執行;否則執行onTouchEvent。
2、onTouchEvent中的DOWN,MOVE,UP
- DOWN時:
a、首先設置標志為PREPRESSED,設置mHasPerformedLongPress=false ;然后發出一個115ms后的mPendingCheckForTap;
b、如果115ms內沒有觸發UP,則將標志置為PRESSED,清除PREPRESSED標志,同時發出一個延時為500-115ms的,檢測長按任務消息;
c、如果500ms內(從DOWN觸發開始算),則會觸發LongClickListener:
此時如果LongClickListener不為null,則會執行回調,同時如果LongClickListener.onClick返回true,才把mHasPerformedLongPress設置為true;否則mHasPerformedLongPress依然為false;
MOVE時:
主要就是檢測用戶是否劃出控件,如果劃出了:
115ms內,直接移除mPendingCheckForTap;
115ms后,則將標志中的PRESSED去除,同時移除長按的檢查:removeLongPressCallback();UP時:
a、如果115ms內,觸發UP,此時標志為PREPRESSED,則執行UnsetPressedState,setPressed(false);會把setPress轉發下去,可以在View中復寫dispatchSetPressed方法接收;
b、如果是115ms-500ms間,即長按還未發生,則首先移除長按檢測,執行onClick回調;
c、如果是500ms以后,那么有兩種情況:
i.設置了onLongClickListener,且onLongClickListener.onClick返回true,則點擊事件OnClick事件無法觸發;
ii.沒有設置onLongClickListener或者onLongClickListener.onClick返回false,則點擊事件OnClick事件依然可以觸發;
d、最后執行mUnsetPressedState.run(),將setPressed傳遞下去,然后將PRESSED標識去除;
最后問個問題,然后再運行個例子結束:
1、setOnLongClickListener和setOnClickListener是否只能執行一個
不是的,只要setOnLongClickListener中的onClick返回false,則兩個都會執行;返回true則會屏幕setOnClickListener
Android ViewGroup事件分發機制
大體的事件流程為:
MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent
可以看出,在View上觸發事件,最先捕獲到事件的為View所在的ViewGroup,然后才會到View自身~
1、ACTION_DOWN中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則找到包含當前x,y坐標的子View,賦值給mMotionTarget,然后調用 mMotionTarget.dispatchTouchEvent
2、ACTION_MOVE中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則直接調用mMotionTarget.dispatchTouchEvent(ev)
3、ACTION_UP中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則直接調用mMotionTarget.dispatchTouchEvent(ev)
當然了在分發之前都會修改下坐標系統,把當前的x,y分別減去child.left 和 child.top ,然后傳給child;
關于攔截
如何攔截
復寫ViewGroup的onInterceptTouchEvent方法:
@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev){int action = ev.getAction();switch (action){case MotionEvent.ACTION_DOWN://如果你覺得需要攔截return true ; case MotionEvent.ACTION_MOVE://如果你覺得需要攔截return true ; case MotionEvent.ACTION_UP://如果你覺得需要攔截return true ; }return false;}默認是不攔截的,即返回false;如果你需要攔截,只要return true就行了,這要該事件就不會往子View傳遞了,并且如果你在DOWN retrun true ,則DOWN,MOVE,UP子View都不會捕獲事件;如果你在MOVE return true , 則子View在MOVE和UP都不會捕獲事件。
原因很簡單,當onInterceptTouchEvent(ev) return true的時候,會把mMotionTarget 置為null ;
如何不被攔截
如果ViewGroup的onInterceptTouchEvent(ev) 當ACTION_MOVE時return true ,即攔截了子View的MOVE以及UP事件;
此時子View希望依然能夠響應MOVE和UP時該咋辦呢?
Android給我們提供了一個方法: requestDisallowInterceptTouchEvent(boolean) 用于設置是否允許攔截,我們在子View的dispatchTouchEvent中直接這么寫:
getParent().requestDisallowInterceptTouchEvent(true); 這樣即使ViewGroup在MOVE的時候return true,子View依然可以捕獲到MOVE以及UP事件。
注
如果ViewGroup在onInterceptTouchEvent(ev) ACTION_DOWN里面直接return true了,那么子View是木有辦法的捕獲事件的~~~
如果沒有找到合適的子View
1、ACTION_DOWN的時候,子View.dispatchTouchEvent(ev)返回的為false ;
則不處理,向上傳遞,由父view處理
總結
關于代碼流程上面已經總結過了~
1、如果ViewGroup找到了能夠處理該事件的View,則直接交給子View處理,自己的onTouchEvent不會被觸發;
2、可以通過復寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給自己處理,則會執行自己對應的onTouchEvent方法
3、子View可以通過調用 getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup對其MOVE或者UP事件進行攔截;
好了,那么實際應用中能解決哪些問題呢?
比如你需要寫一個類似slidingmenu的左側隱藏menu,主Activity上有個Button、ListView或者任何可以響應點擊的View,你在當前View上死命的滑動,菜單欄也出不來;因為MOVE事件被子View處理了~ 你需要這么做:在ViewGroup的dispatchTouchEvent中判斷用戶是不是想顯示菜單,如果是,則在onInterceptTouchEvent(ev)攔截子View的事件;自己進行處理,這樣自己的onTouchEvent就可以順利展現出菜單欄了~
參考鏈接
Android View 事件分發機制 源碼解析 (上) - Hongyang - 博客頻道 - CSDN.NET
Android ViewGroup事件分發機制 - Hongyang - 博客頻道 - CSDN.NET
總結
以上是生活随笔為你收集整理的Android之事件分发机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过SVD求解单应矩阵
- 下一篇: React Native实例