網上有不少資料對事件的分發過程作了詳盡的代碼追蹤,好比 www.jianshu.com/p/38015afcd…bash
有興趣的同窗能夠參考並去詳細走一下,這裏我作一個文字性描述:ide
關於第七、8兩點,ViewGroup是如何在 dispatchTouchEvent 過程當中快速命中並分發到對應子 View 的呢?這裏是經過 TouchTarget 這個結構來實現的。函數
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
// 用於控制同步的鎖
private static final Object sRecycleLock = new Object[0];
// 注意這是static類型的,內部可複用實例鏈表表頭
private static TouchTarget sRecycleBin;
// 內部可複用的實例鏈表的長度
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// 當前被觸摸的 View
public View child;
// 對目標捕獲的全部指針的指針id的組合位掩碼
public int pointerIdBits;
// 鏈表中指向的下一個目標
public TouchTarget next;
private TouchTarget() {
}
...
}
複製代碼
在ViewGroup中維護了一個變量:mFirstTouchTarget,這是在 ViewGroup 中維護的鏈表, 用於記錄當前響應事件序列的子 View (一個事件序列對應一個響應它的子View),mFirstTouchTarget 指向鏈表首部。ui
先看一下 mFirstTouchTarget 的賦值:this
// 這是發生在ViewGroup中的dispatchTouchEvent方法中
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
}
// 當響應事件的目標child View添加到鏈表中,同時讓 mFirstTouchTarget 指向鏈表的表頭
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
複製代碼
再看 mFirstTouchTarget 在 dispatchTouchEvent 方法中的使用:spa
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
一、若是事件是 ACTION_DOWN 事件,重置 touchTargets 狀態,在 cancelAndClearTouchTargets 方法中會發出 ACTION_CANCEL 事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
二、對於一個事件序列,當其中某一個事件成功攔截時,那麼對於剩下的一系列事件也會被攔截,而且不會再次執行onInterceptTouchEvent方法。若是 ACTION_DOWN 事件被攔截了,即當前ViewGroup的 onInterceptTouchEvent(ev) return true;此時 mFirstTouchTarget 必然爲null,後續的事件都會當前 ViewGroup 攔截再也不傳遞
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
三、若是事件既沒有cancel,也沒有被 intercept,遍歷子View進行事件分發
if (!canceled && !intercepted) {
...
}
四、事件分發過程當中,若是dispatchTouchEvent返回了false,或者說當前的ViewGroup沒有子元素的話,會走到這個邏輯。mFirstTouchTarget == null說明子View並無消費事件,因此沒有對mFirstTouchTarget進行賦值。這裏child == null,代碼會進一步執行super.dispatchTouchEvent(event),即 View 中的 dispatchTouchEvent 方法
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
五、mFirstTouchTarget != null, 說明事件被子View消費,此時會依次將事件分發到 mFirstTouchTarget 保存的鏈表 View中
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
...
target = next;
}
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
複製代碼
這個地方重點關注一下一、二、三、四、5幾個註釋點。如今咱們回到7 8兩點。代理
若是View 沒有對 ACTION_DOWN 進行消費,這次點擊的後續事件不會傳遞過來。這個很顯然,若是沒有對 ACTION_DOWN 進行消費,就不會被保存到 TouchTarget 鏈表中,後續事件的分發是直接往這個鏈表中進行分發的。指針
若是 View 消費了 ACTION_DOWN ,這次點擊的後續事件會直接給這個 View,這裏的後續事件指的是 ACTION_MOVE 和 ACTION_UP 事件;此時,其父 ViewGroup 的 onIntercept 函數仍會被調用,仍能進行攔截,但它本身的 onIntercept 不會被調用了。這個能夠從第2點註釋中找到答案,若是事件被消費了,mFirstTouchTarget != null, 後續事件能夠從mFirstTouchTarget 鏈表中直接分發,同時後續事件過來的時候會跳過intercepted 的判斷,因此本身的 onIntercept 就不會調用了。rest
這裏以點擊 RecyclerView 中的某個Item中的 Button 爲例:code
點下Button
移動點擊按鈕的時候
手指擡起 前面創建了一個view鏈表,RecyclerView 的父view在獲取事件的時候,會直接取鏈表中的RecyclerView 讓其進行事件消耗
有興趣的同窗能夠帶着這個步驟去追蹤 RecyclerView 的源代碼。
多點觸控涉及到了多個手指點擊事件的處理,這裏要增長兩個額外的事件
事件類型: ACTION_POINTER_UP; active pointer index: 0; pointer: x: 200, y: 300, index: 0, id: 1; pointer: x: 300, y: 500, index: 1, id: 2
什麼是滑動衝突?就是父 View 和子 View 都須要處理滑動,例如父 View 須要左右滑動,子 View 須要上下滑動(ViewPager 嵌套 RecyclerView),一個點擊事件,到底交給誰處理?
首先咱們須要定義好處理規則,而後咱們在父 View 的 onIntercept、子 View 的 onTouchEvent 以及父 View 的 onTouchEvent 函數中實現咱們定義的規則便可。例如父 View 的 onIntercept 中,若是發現是左右滑動,那就攔截,不然不攔截。
NestedScrollView 嵌套 RecyclerView 也是同樣的道理,NestedScrollView 發現是上下滑動,就直接攔截並處理,RecyclerView 就沒有處理的機會了。
參考文章