文章目錄
基本知識
事件傳遞的三個主體
Activity、ViewGroup、View
他們三個的嵌套關係通常是這樣:
可是還要明白的是:
java
- ViewGroup固然能夠嵌套ViewGroup,即ViewGroup也能夠是另外一個ViewGroup的子View。
- ViewGroup實際上是繼承於View,是View的子類。
事件分發機制相關三個經典函數
- dispatchTouchEvent():分發函數
- onInterceptTouchEvent():攔截函數
- onTouchEvent():消費函數
它們的功能和它們名字同樣。其中攔截函數是ViewGroup獨有的,其它兩個函數在上面說的三個主體都存在。函數
事件分發機制四個經典事件
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
- ACTION_CANCLE
意如其名源碼分析
事件分發機制場景
不攔截、不消費
當三個主體的任何函數的 返回值 都不作任何處理時,即不攔截、不消費:動畫
可見:
ui
- 對於down事件:我會從外層一層層地分發下去(Activity->ViewGroup->view),看看
- down不消費,move,up我就不傳遞去了
ViewGroup攔截、無消費
當在ViewGroup使onInterceptTouchEvent()返回true,即ViewGroup對事件進行攔截時:this
可見:
spa
- 事件被攔截以後就不會往下分發
ViewGoup消費,不攔截
當在ViewGroup使onTouchEvent()返回true,即ViewGroup對事件進行消費時:
可見:
.net
- 當down被消費了就不會往上冒
- move up不會往下發,而是直接分發給消費者。
源碼分析
具體代碼怎麼實現?主要是看分發函數dispatchTouchEvent(),接下來咱們看看 三個主體的dispatchTouchEvent() 源碼分析code
Activity的dispatchTouchEvent()源碼
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();//這是一個空方法 } //主要看這一句 //getWindow().superDispatchTouchEvent(ev) //這句函數調用的時DecorView的superDispatchTouchEvent() //而DecorView繼承於ViewGroup if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
代碼中我寫了註釋,咱們能夠得出下面的結論:
- getWindow().superDispatchTouchEvent(ev),實際是調用了一個ViewGroup的dispatchTouchEvent()
- getWindow().superDispatchTouchEvent(ev)返回true,說明有子View消費該事件(爲何呢?咱們要分析完ViewGroup的dispatchTouchEvent()才知道,但如今能夠暫時給出這個結論);這個子View多是某個ViewGroup或者View。
- 若是有子view消費該事件則返回true,不然調用自身的onTouchEvent(ev),即把事件分發給本身。
ViewGroup的dispatchTouchEvent()源碼
ViewGroup的dispatchTouchEvent()源碼很長,我參考了https://blog.csdn.net/wolinxuebin/article/details/53057075
以後得出一些小結,如今貼出一部分,一段一段分析。
整體邏輯分析
我把部分代碼和具體邏輯去掉,看它的空架子
//這是一個單鏈表,我暫時理解爲用於存放響應了DOWN的事件 private TouchTarget mFirstTouchTarget; public boolean dispatchTouchEvent(MotionEvent ev) { ... //判斷是不是模糊窗口,若是是窗口,則表示不但願處理改事件。(如dialog後的窗口) if (onFilterTouchEventForSecurity(ev)) { // 清空以前的狀態 if (actionMasked == MotionEvent.ACTION_DOWN) { } //檢查是否須要攔截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { } else { } //檢查是否要取消,即標記了PFLAG_CANCEL_NEXT_UP_EVENT 或者 當前是一個Cancel事件 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; //不用取消,無需攔截,則進行事件分發 if (!canceled && !intercepted) { //分發DOWN事件 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //分發DOWN給child if (newTouchTarget == null && childrenCount != 0) { } //若是沒有child相應該事件,則將此事件交給最近加入的target? //這裏不是很懂,若是沒有child響應,那麼mFirstTouchTarget也是null呀 if (newTouchTarget == null && mFirstTouchTarget != null) { } } } //mFirstTouchTarget爲空代表沒有child響應這個事件,則分發給本身 if (mFirstTouchTarget == null) { } //按照mFirstTouchTarget分發 else { } } ... //若是自身或者child消費了事件則返回true,不然返回false return handled; }
關於解析看代碼中的註釋。接下來看看其中幾段邏輯具體怎麼實現的。
具體分析一
看一下分發給DOWN給子View的具體邏輯:
//分發DOWN給child if (newTouchTarget == null && childrenCount != 0) { // 對子Views進行排序,有兩種方式:一、按照Z軸,二、按照draw final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //遍歷子View for (int i = childrenCount - 1; i >= 0; i--) { //這裏兩行代碼,簡單的理解根據不一樣的排列選項(一、view添加到 二、view的draw順序 三、viewZ 軸順序) final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //canViewReceivePointerEvents 判斷child是否爲visiable 或者 是否有動畫 //isTransformedTouchPointInView 判斷x, y是否在view的區域內(若是是執行了補間動畫 則x,y會經過獲取的matrix變換值 // 換算當相應的區域,這也是爲何補間動畫的觸發區域不隨着動畫而改變) if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } //若是chile已經在mFirstTouchTarget單鏈表裏面,結束循環 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } //判斷child的dispatchTouchEvent()是否會返回true,若是是true,將child加入單鏈表,而後結束循環 //dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)會調用child的dispatchTouchEvent() resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // 找到childIndex所表明的child的最原始的index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } //將相應該事件的child包裝成一個Target,添加到mFirstTouchTarget鏈表中 mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } }
這裏我只貼出部分的代碼,解析都在註釋,能夠看到,mFirstTouchTarget鏈表只存在一個值,就是響應了事件的那個child。
具體分析二
//這一段的要麼分發down給本身要麼按照單鏈表分發move、up //僞代碼 // if (mFirstTouchTarget == null) { //沒有child響應事件,則分發給本身,handled將做爲返回值返回。 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; //在前面Down事件處理中,已經將這個事件交給newTouchTarget處理過了,就不重複處理了 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //這裏實際上是分發move和up事件,由於down事件在前面已經處理完了,不會進入這裏 //再次斷定是否須要cancel,由於有可能在move過程事件被攔截 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } //若是cancel,回收鏈表節點空間,最後使mFirstTouchTarget置null if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
View的dispatchTouchEvent()源碼
一樣的,我省略了部分代碼,解析看註釋
public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (onFilterTouchEventForSecurity(event)) { //若是設置了onTouchListener,會先調用onTouch() ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //調用onTouchEvent() if (!result && onTouchEvent(event)) { result = true; } } return result; }
能夠看到:
- View的dispatchTouchEvent()的返回值取決於onTouch()或onTouchEvent(),也就是有沒有消費該事件。
- 若是onTouch()返回true的話就不會調用onTouchEvent(),這就是爲何有些博客寫onTouch()優先於onTouchEvent()。
總結
分析源碼就能夠知道前面說的三種場景是怎麼回事了。
有點像皇帝派任務,皇帝說如今有個好活兒,可是不知道誰要接這個活兒,因而派了一個小太監去探測一下;
小太監先去找宰相,宰相又讓他去找知府,知府讓他去找衙門小兵。
這裏面皇帝就像Acticity、各級官員就像ViewGroup、小兵就像View、而小太監就像DOWN事件,活兒就是跟着DOWN後面的MOVE和UP事件。
- 不攔截、不消費:小太監一層層找到小兵後,沒有一個小兵想接這個活兒(一層層分發DOWN事件),因而小兵沿路返回報告給知府、知府也不想作就報告給宰相,宰相不想作就回去報告給皇帝,皇帝說沒人作那我看看本身能不能作吧(DOWN事件回到Activity派給本身,MOVE、UP也再也不分發而是直接派給本身)
- ViewGroup攔截、不消費:宰相讓小太監找到知府的時候, 這個知府有點霸道直接把小太監攔下了,小太監就就再也不繼續通知下級人員了(攔截事件)。可是呢這個官員只是單純攔下了小太監但他並不想接這個活兒,因而小兵仍是沿路回去報告給說下面沒人接活,最終仍是傳回給皇帝說沒人作那我看看本身能不能作(MOVE、UP再也不分發直接派給本身)
- ViewGroup消費、不攔截:一樣,無人攔截的話,小太監一層層找到小兵,發現小兵沒人想作,就回去報告知府,這時候知府說小兵不作我來作(DOWM在這裏被消費了)。而後知府寫信報告宰相說這活兒我接了(返回true),宰相又報告皇帝說下面有人接受任務了,然會皇帝下次就直接派發任務給宰相,宰相找到那個願意接受任務知府,把任務派給他。(派發MOVE、UP)
分發函數分發DOWN時其實有點相似於遞歸的方式,只不過不是本身調用本身,而是一層層地調用child的同名函數。分發MOVE、UP則再也不一一詢問,而是根據DOWN是否被消費進行分發。
源碼很長如今頭都有點亂,那麼從源碼能夠學習到什麼呢,你們幫忙補充吧
- 若有有這種嵌套式的應答需求,能夠學習ViewGroup的分發函數,用相似於遞歸的方式提問和接收應答。
- 對於較大量的信息,命令傳送,能夠先派一個小兵嗅探一下,記錄可行的路線,後續信息按路線分發。