Android事件分發機制之原始碼完美解析(上)
學事件分發是為了什麼呢?還不是為了解決滑動衝突的。
實際上,如果僅僅是為了解決滑動衝突的,大可不必看原始碼,只需要掌握事件分發的外在規律即可。
只要記住這張圖,再明白內部攔截法和外部攔截法,滑動衝突這一塊,都可以輕鬆解決了。
分享一個非常好的滑動衝突的例項:http://blog.csdn.net/qq_36523667/article/details/78825810
只需要掌握上述的內容,事件分發再無難題了。
但是,如果想掌握的更通透一點,原始碼是不可或缺的。因為原始碼裡還有很多細節,enable?clickable?performClick?。。。
最重要的一個問題,如果在自定義一個view的時候,onTouchEvent返回了true,代表是消費這系列事件。而且onTouchEvent不會再呼叫父容器的onTouchEvent;如果返回false,會轉而呼叫父容器的onTouchEvent。這個問題是事件分發的核心問題,可是在原始碼中哪裡有答案???
在view group裡有一段核心的程式碼
for (int i = childrenCount - 1; i >= 0; i--) {...
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. 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; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }上面會有一個for迴圈,進行手指觸控區域的查詢,看看是否會落在child裡面。找到那個手指所落在的那個child view裡。然後執行child.dispatchTouchEvent。進行一個遞迴查詢。(為什麼自動就進行一個遞迴查詢了呢?別看這裡是view哦,以為是直接就呼叫了view的dispatchTouchEvent。錯誤!比如你這個child查詢到是FrameLayout,然後你其實是一個view group,所以你的dispatch touch event 肯定是view group的dispatch touch event(因為view group重寫了view的dispatchTouchEvent))
所以for迴圈的任務是:找到當前view group中所有view、view group的區域中包含該手指落點的view、viewgroup,並進行一個遞迴的分發。
不過不是還有一種找不到的情況嘛!那種情況大部分是mFirstTouchTarget為空。
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }也就是上面的這段程式碼。而下面的else總體上就是指代的CANCEL情況,不過本文討論的重點不是CACEL。
這種情況就是1.沒有child 2.child不需要 這個時候由這個view group 呼叫super.dispatchTouchEvent。即把這個view group作為view,呼叫他自身的view.dispatchTouchEvent。
下面著重講講子view不為空的情況,去解決那個核心問題。
重新提一下問題:如果子view返回true,為什麼就不呼叫父view的邏輯了?如果子view返回false,為什麼就呼叫父view的邏輯了?
其實上面已經給出了答案。上面的原始碼先是一個for迴圈,那個for迴圈考慮的就是子view的情況;下面有一個if,if考慮的就是父view的情況。
如果1.view group有子view 2.且這個子view返回了true,願意接收這個事件 這種情況mFirstTouchTarget就不為空了。所以下面的if判斷條件就不成立了。而如果1.view group沒有子view 2.或者這個子view 返回了 false。這個時候mFirstTouchTarget就是空了。自然就只能交給view group來處理了。
現在用最正宗的虛擬碼概括一下view group的程式碼:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { for (;;) { 遞迴所有的子View 1.有子View返回了true進行消費 mFirstTouchTarget = xxx 2.沒有 } if (mFirstTouchTarget == null) { super.dispatchTouchEvent(ev); } else { CANCLE的情況 } }
接下來我們用原始碼作最後的證明:為什麼子View的onTouchEvent的返回值可以決定mFirstTouchTarget?
上面的for迴圈的邏輯虛擬碼如下
TouchTarget newTouchTarget; for (;;) { if (dispatchTransformedTouchEvent(...)) { newTouchTarget = addTouchTarget(child...); } }所以我們需要檢視一下,addTouchTarget方法的原始碼
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }所以在這裡就完成了mFirstTouchTarget的賦值。(這裡解釋下mFirstTouchTarget的概念,他在view group中的dispatch程式碼中起了十分重要的作用。least recently added target。意思就是最近在for迴圈中搜到的並且願意消費的view。)
解決了上述問題,繼續追溯虛擬碼中的dispatchTransformedTouchEvent方法:
// Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); }這裡是dispatchTransformedTouchEvent中的核心程式碼。這裡是遞迴呼叫邏輯的具體實現。直接看else吧。因為for迴圈中傳入的引數是child。(if中傳入的child引數才為空)所以直接看view的dispatch程式碼就可以了。
view的dispatch的核心程式碼
if (!result && onTouchEvent(event)) { result = true; }
return result;
所以view的dispatch的返回值是由view的onTouchEvent來決定的。
所以,得出結論:如果view的onTouchEvent返回true,view的dispatch多半也會返回true,然後父view group的dispatchTransformedTouchEvent多半也會返回true,然後就mFirstTouchTarget也會被賦值,因此父view group的邏輯就不會被執行了;反之,view的onTouchEvent...然後mFirstTouchTarget依然為空,因此父view group的邏輯就會被得到執行。
到這裡告一段落,這篇文章主要分析事件分發的核心機制,讓我們不僅僅停留在會用的基礎上,也知其所以然了。下篇文章會像流水賬一樣分析所有事件分發的程式碼。
相關文章
- Android 事件分發機制原始碼解析Android事件原始碼
- Android 事件分發機制原始碼解析-view層Android事件原始碼View
- Android事件分發機制解析Android事件
- 【Android原始碼】View的事件分發機制Android原始碼View事件
- Android事件分發:從原始碼角度分析View事件分發機制Android事件原始碼View
- Android事件分發機制完全解析,帶你從原始碼的角度徹底理解(上)Android事件原始碼
- 基於原始碼分析 Android View 事件分發機制原始碼AndroidView事件
- Android 事件分發機制原始碼詳解-最新 APIAndroid事件原始碼API
- Android從原始碼角度剖析View事件分發機制Android原始碼View事件
- 事件分發機制(二):原始碼篇事件原始碼
- vue原始碼解析-事件機制Vue原始碼事件
- 圖解Android事件分發機制(深入底層原始碼)圖解Android事件原始碼
- Android事件分發機制Android事件
- TouchEvent事件分發機制全解析事件
- Android事件分發機制探究Android事件
- vscode原始碼分析【五】事件分發機制VSCode原始碼事件
- Android事件分發機制三:事件分發工作流程Android事件
- android事件分發機制詳解Android事件
- Android的MotionEvent事件分發機制Android事件
- Android 事件分發機制的理解Android事件
- 完全理解android事件分發機制Android事件
- 圖解 Android 事件分發機制圖解Android事件
- Android ViewGroup事件分發機制AndroidView事件
- React事件機制 - 原始碼概覽(上)React事件原始碼
- Spring事件監聽機制原始碼解析Spring事件原始碼
- Dubbo原始碼解析之SPI機制原始碼
- Android事件分發機制本質是樹的深度遍歷(圖+原始碼)Android事件原始碼
- Android AccessibilityService機制原始碼解析Android原始碼
- Android View 的事件體系 -- 事件分發機制AndroidView事件
- Android事件分發機制簡單理解Android事件
- 淺談Android 事件分發機制(二)Android事件
- 10分鐘理解 Android View 事件分發機制AndroidView事件
- 從原始碼看 Android 事件分發原始碼Android事件
- Android View 事件分發原始碼分析AndroidView事件原始碼
- Android事件分發原始碼歸納Android事件原始碼
- JDK原始碼解析之Java SPI機制JDK原始碼Java
- View事件分發機制View事件
- 淺談Android中的事件分發機制Android事件