參考文檔
API
- ViewGroup的觸摸事件處理,很多繼承于view,一方面它重載了dispatchTouchEvent,另外一個主要的區(qū)別在于新添加了函數(shù)onInterceptTouchEvent
dispatchTouchEvent
- 如果ViewGroup的某個孩子沒有接受ACTION_DOWN事件丈攒;那么蜒程,ACTION_MOVE和ACTION_UP等事件也一定不會分發(fā)給這個孩子
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 調(diào)試用
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
// 第1步:根據(jù)遮擋情況,判斷是否需要分發(fā)該觸摸事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 第2步:檢測是否需要清空目標和狀態(tài)
//
// 如果是ACTION_DOWN,說明是一個新的觸摸事件序列,則清空之前的觸摸事件處理target和狀態(tài)。
// 這里的情況狀態(tài)包括:
// (01) 清空mFirstTouchTarget鏈表乖寒,并設置mFirstTouchTarget為null。
// mFirstTouchTarget是"接受觸摸事件的View"所組成的單鏈表
// (02) 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT標記
// 如果設置了FLAG_DISALLOW_INTERCEPT院溺,則不允許ViewGroup對觸摸事件進行攔截宵统。
// (03) 清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN標記
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 第3步:檢查當前ViewGroup是否想要攔截觸摸事件
//
// 如果是ACTION_DOWN,也就是一個事件序列的開始,當然要重新判斷當前ViewGroup是否攔截了事件
// 而如果不是ACTION_DOWN事件覆获,并且mFirstTouchTarget為null的話马澈,也就是說前面的ACTION_DOWN事件不是由子view去處理的,
// 因而以后的ACTION_UP,ACTION_MOVE等事件也不會交由子view去處理,所以就相當于直接攔截了事件弄息,直接返回了true
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 檢查禁止攔截標記:FLAG_DISALLOW_INTERCEPT
// 如果調(diào)用了requestDisallowInterceptTouchEvent()標記的話痊班,則FLAG_DISALLOW_INTERCEPT會為true,不允許攔截
// 例如,ViewPager在處理scroll事件的時候摹量,就會調(diào)用requestDisallowInterceptTouchEvent()涤伐,
// 禁止它的父類對觸摸事件進行攔截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 允許自身攔截的話馒胆,返回onInterceptTouchEvent()
intercepted = onInterceptTouchEvent(ev);
// restore action in case it was changed
ev.setAction(action);
} else {
//不允許攔截的話,當然也就沒有攔截
intercepted = false;
}
} else {
//up, move事件凝果,并且沒有子view可以處理down,當然也沒有子view處理up,move,直接相當于攔截掉了
intercepted = true;
}
// 第4步:檢查當前的觸摸事件是否被取消
//
// (01) 對于ACTION_DOWN而言祝迂,mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVENT位肯定是0;因此器净,canceled=false型雳。
// (02) 當前的View或ViewGroup要被從父View中detach時,PFLAG_CANCEL_NEXT_UP_EVENT就會被設為true山害;
// 此時纠俭,它就不再接受觸摸事情。
final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 第5步:將觸摸事件分發(fā)給"當前ViewGroup的子View和子ViewGroup"
//
// 如果觸摸"沒有被取消"浪慌,同時也"沒有被攔截"的話冤荆,則將觸摸事件分發(fā)給它的子View和子ViewGroup。
// 整一個if(!canceled && !intercepted){ … }代碼塊所做的工作就是對ACTION_DOWN事件的特殊處理权纤。
// 因為ACTION_DOWN事件是一個事件序列的開始钓简,所以我們要先找到能夠處理這個事件序列的一個子View,
// 如果一個子View能夠消耗事件汹想,那么mFirstTouchTarget會指向子View涌庭,
// 如果所有的子View都不能消耗事件,那么mFirstTouchTarget將為null
if (!canceled && !intercepted) {
//MotionEvent.ACTION_HOVER_MOVE一般指鼠標事件欧宜,鼠標在view上面
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 這是獲取觸摸事件的序號 以及 觸摸事件的id信息坐榆。
// 對于down事件,一般是0
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// 清空這個手指之前的TouchTarget鏈表冗茸。
// 一個TouchTarget席镀,相當于一個可以被觸摸的對象;它中記錄了接受觸摸事件的View
removePointersFromTouchTargets(idBitsToAssign);
// 獲取該ViewGroup包含的View和ViewGroup的數(shù)目夏漱,
// 然后遞歸遍歷ViewGroup的孩子豪诲,對觸摸事件進行分發(fā)。
// 遞歸遍歷ViewGroup的孩子:是指對于當前ViewGroup的所有孩子挂绰,都會逐個遍歷屎篱,并分發(fā)觸摸事件;
// 對于逐個遍歷到的每一個孩子葵蒂,若該孩子是ViewGroup類型的話交播,則會遞歸到調(diào)用該孩子的孩子,...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
//倒序遍歷践付,一般都是希望最上面的反饋
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
// 如果child不能夠接受觸摸事件秦士,又或者觸摸坐標(x,y)在child的可視范圍之外
// 就繼續(xù)循環(huán)查找可以分發(fā)事件的子View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// getTouchTarget()的作用是查找child是否存在于mFirstTouchTarget的單鏈表中。
// 是的話永高,則更新相應的值
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 重置child的mPrivateFlags變量中的PFLAG_CANCEL_NEXT_UP_EVENT位隧土。
resetCancelNextUpFlag(child);
// 將事件嘗試分發(fā)給子View,如果找到了一個可以處理觸摸事件的子View提针,就直接跳出循環(huán),不用繼續(xù)遍歷
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果child能夠接受該觸摸事件曹傀,即child消費或者攔截了該觸摸事件的話辐脖;
// 則調(diào)用addTouchTarget()將child添加到mFirstTouchTarget鏈表的表頭,并返回表頭對應的TouchTarget
// 同時還設置alreadyDispatchedToNewTouchTarget為true皆愉。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
// 如果newTouchTarget為null嗜价,并且mFirstTouchTarget不為null;
// 則設置newTouchTarget為mFirstTouchTarget鏈表中第一個不為空的節(jié)點亥啦。
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 第6步:進一步的對觸摸事件進行分發(fā),實際上是對攔截的了炭剪,取消了的练链,或其它非ACTION_DOWN事件的處理
//
if (mFirstTouchTarget == null) {
// 如果mFirstTouchTarget為null翔脱,意味著還沒有任何View來接受該觸摸事件;
// 此時媒鼓,將當前ViewGroup看作一個View届吁;
// 將會調(diào)用"當前的ViewGroup的父類View的dispatchTouchEvent()"對觸摸事件進行分發(fā)處理。
// 注意:這里的第3個參數(shù)是null绿鸣,也就是直接將事件交由這個ViewGroup處理
// 即疚沐,會將觸摸事件交給當前ViewGroup的onTouch(), onTouchEvent()進行處理。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
// 如果mFirstTouchTarget不為null潮模,說明ACTION_DOWN事件已經(jīng)有子View處理了亮蛔,
// 那么對于其它類型的事件,直接嘗試分發(fā)給mFirstTouchTarget鏈表中的就可以了
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 這里實際上區(qū)分開了ACTION_DOWN與其它類型的事件
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;
}
}
// 第7步:再次檢查取消標記擎厢,并進行相應的處理
//
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
// 調(diào)試用
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
onInterceptTouchEvent
onInterceptTouchEvent是否攔截觸摸事件究流,默認情況下是不攔截的
-
常見的例子就是ViewPager,當發(fā)生滑動事件的時候,ViewPager攔截了事件动遭,用來左右滑動item
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
當ViewGroup攔截了down事件芬探,或者沒有子view去處理down事件,后續(xù)的up,move事件就不會調(diào)用 onInterceptTouchEvent()方法了厘惦,所以該方法并不是每次事件都會調(diào)用的
圖解
總結
- ACTION_DOWN事件為一個事件序列的開始偷仿,中間有若干個ACTION_MOVE,最后以ACTION_UP結束。
- ViewGroup默認不攔截任何事件宵蕉,所以事件能正常分發(fā)到子View處(如果子View符合條件的話)酝静,如果沒有合適的子View或者子View不消耗ACTION_DOWN事件,那么接著事件會交由ViewGroup處理羡玛,并且同一事件序列之后的事件不會再分發(fā)給子View了形入。如果ViewGroup的onTouchEvent也返回false,即ViewGroup也不消耗事件的話缝左,那么最后事件會交由Activity處理亿遂。即:逐層分發(fā)事件下去浓若,如果都沒有處理事件的View,那么事件會逐層向上返回蛇数。
- 如果某一個View攔截了事件挪钓,那么同一個事件序列的其他所有事件都會交由這個View處理,此時不再調(diào)用View(ViewGroup)的onIntercept()方法去詢問是否要攔截了