參考資料
鴻洋版ViewGroup事件分發(fā)機(jī)制
郭霖版ViewGroup事件分發(fā)機(jī)制
Android開發(fā)藝術(shù)探索
上一篇已經(jīng)分析了Android View的事件分發(fā)機(jī)制虑省,本篇將根據(jù)源碼講解ViewGroup的事件分發(fā)機(jī)制螟凭,View的一大難題是滑動(dòng)沖突称龙,滑動(dòng)沖突的理論基礎(chǔ)就是事件分發(fā)機(jī)制启摄,所以了解事件分發(fā)機(jī)制凌外,也有益于大家了解沖突產(chǎn)生的原因蹦玫,以及對(duì)沖突進(jìn)行處理。
關(guān)于事件傳遞機(jī)制只锻,這里先給出一些結(jié)論玖像,根據(jù)這些結(jié)論可以更好的理解整個(gè)傳遞機(jī)制:
(1)同一事件序列是指從手指觸摸屏幕的那一刻起,最手指離開屏幕的那一刻結(jié)束炬藤,在整個(gè)過程中
所產(chǎn)生的一系列事件,這個(gè)事件序列以down事件開始碴里,中間含有不定數(shù)量的move沈矿,最終以u(píng)p事件結(jié)束;
(2)事件傳遞都是由外向內(nèi)傳遞的咬腋,即事件總是先傳遞給父view羹膳,然后再由父View傳遞給子view;
通過requestDisallowInterceptTouchEvent可以在子元素中干預(yù)父View的時(shí)間分發(fā)過程根竿,但是ACTION_DOWN除外陵像;
(3)如果某個(gè)View一旦開始攔截某個(gè)事件,那么同一事件序列都只能由他來處理寇壳,
并且它的onInterceptTouchEvent不會(huì)再次被調(diào)用醒颖。
(4)View沒有onInterceptTouchEvent方法,一旦有事件傳遞給它壳炎,那么它的onTouchEvent就會(huì)被調(diào)用泞歉,
View的ontouchEvent默認(rèn)都會(huì)消耗事件,viewGroup默認(rèn)不攔截事件匿辩,Android源碼中可以看到onInterceptTouchEvent
默認(rèn)返回值為false腰耙;
(5)子元素是否可以接受點(diǎn)擊事件?第一铲球,子元素是否在做動(dòng)畫挺庞;第二,點(diǎn)擊事件的坐標(biāo)是否落在子元素區(qū)域中稼病。
(6)如果所有子元素都沒有處理事件选侨,這里包含兩種情況,第一ViewGroup沒有子元素然走,第二子元素處理了點(diǎn)擊事件侵俗,但是在dispatchTouchEvent中返回false,一般是因?yàn)樽釉卦趏nTouchEvent返回false丰刊,這兩種情況ViewGroup會(huì)自己處理點(diǎn)擊事件
(7)事件大體傳遞流程:VeiwGroup的dispatchTouchEvent -> VeiwGroup的onInterceptTouchEvent ->View的dispatchTouchEvent ->View的onTouchEvent
源碼解析
ViewGroup.dispatchTouchEvent():
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
// 處理原始的DOWN時(shí)間
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
// 需要在新事件開始時(shí)處理完上一個(gè)事件隘谣,并且調(diào)用resetTouchState重置狀態(tài)
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
// 檢查當(dāng)前View是否攔截事件
// ViewGroup在如下兩種情況下會(huì)判斷是否攔截當(dāng)前事件:事件類型為down或者mFirstTouchTarget != null。
// 當(dāng)ViewGroup不攔截事件并將事件交由子元素處理時(shí),mFirstTouchTarget會(huì)被賦值也就是mFirstTouchTarget != null寻歧。
// 這樣當(dāng)move事件和up事件到來時(shí)掌栅,并且事件已經(jīng)被分發(fā)下去,那么onInterceptTouchEvent這個(gè)方法將不會(huì)再被調(diào)用码泛。
//所以當(dāng)前ViewGroup攔截事件之后就不會(huì)再次調(diào)用onInterceptTouchEvent方法猾封;
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調(diào)用onInterceptTouchEvent方法判斷是否攔截當(dāng)前事件,ViewGroup默認(rèn)返回false噪珊;
//如果攔截事件將intercepted置為true晌缘;
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// 如果事件未被取消且未被攔截,如果攔截事件會(huì)將intercepted置為true痢站;
if (!canceled && !intercepted) {
//ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 當(dāng)ViewGroup不攔截事件的時(shí)候磷箕,事件會(huì)向下分發(fā)交由它的子View進(jìn)行處理
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
// 從上至下去尋找一個(gè)可以接收該事件的子View
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍歷ViewGroup的所有子元素,然后判斷子元素是否能夠收到點(diǎn)擊事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
// 是否能夠收到點(diǎn)擊事件主要由兩點(diǎn)來衡量:
// 子元素是否在播放動(dòng)畫和點(diǎn)擊事件的坐標(biāo)是否落在子元素的區(qū)域內(nèi)阵难。
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 子元素是否能夠接收PointerEvent岳枷,或事件有沒有落在子元素的邊界范圍
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
resetCancelNextUpFlag(child);
// 如果某個(gè)子元素滿足條件,那么事件就會(huì)傳遞給它處理呜叫,
// dispatchTransformedTouchEvent這個(gè)方法實(shí)際上就是調(diào)用子元素的dispatchTouchEvent方法空繁,
// 如果子元素仍然是一個(gè)ViewGroup,則遞歸調(diào)用重復(fù)此過程朱庆。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 子View在其邊界范圍內(nèi)接收事件
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果子元素的dispatchTouchEvent返回true盛泡,表示子元素已經(jīng)處理完事件,
// 那么mFirstTouchTarget就會(huì)被賦值同時(shí)跳出for循環(huán)娱颊。
// mFirstTouchTarget的賦值在addTouchTarget內(nèi)部完成
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
}
}
// Dispatch to touch targets.
// 如果遍歷完所有的子元素事件沒有被合適處理饭于,有兩種情況:
// 1. ViewGroup沒有子元素
// 2. 子元素處理了點(diǎn)擊事件,但是dispatchTouchEvent返回false
// 這時(shí)ViewGroup會(huì)自己處理點(diǎn)擊事件维蒙。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 這里的第三個(gè)參數(shù)child為null掰吕,此時(shí)會(huì)調(diào)用handled = super.dispatchTouchEvent(event);
//最終會(huì)調(diào)用ViewGroup自身的onTouchEvent來處理事件;
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;
//將處理該事件的子view復(fù)制給target颅痊,由子元素來處理該事件
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;
//該方法最終會(huì)調(diào)用子元素的dispatchTouchEvent殖熟,傳給給子元素來處理事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//UP事件到來,重置狀態(tài)斑响,例如將處理該事件的子view mFirstTouchTarget置為null菱属;
resetTouchState();
}
}
return handled;
}
說明:
(1)如果onInterceptTouchEvent返回true,表示ViewGroup將攔截事件舰罚,會(huì)將intercepted設(shè)置true纽门,這樣就不會(huì)遍歷子view尋找事件接受者;這樣mFirstTouchTarget為null营罢,會(huì)調(diào)用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (child == null) {
//因?yàn)閭魅氲膮?shù)為null赏陵,所以調(diào)用ViewGroup自身的dispatchTouchEvent方法來處理事件饼齿;
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//當(dāng)有子元素處理事件時(shí),會(huì)調(diào)用子元素的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
(2)當(dāng)子元素處理事件時(shí)會(huì)調(diào)用addTouchTarget()蝙搔;
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
// 由子元素處理事件時(shí)缕溉,將子元素賦值給mFirstTouchTarget
//這樣通過mFirstTouchTarget是否為null,就可以知道事件時(shí)由子view還是ViewGroup來處理事件吃型;
mFirstTouchTarget = target;
return target;
}
(3)如果ViewGroup找到了能夠處理該事件的View证鸥,則直接交給子View處理,自己的onTouchEvent不會(huì)被觸發(fā)勤晚;
(4)可以通過復(fù)寫onInterceptTouchEvent(ev)方法枉层,攔截子View的事件(即return true),把事件交給自己處理赐写,則會(huì)執(zhí)行自己對(duì)應(yīng)的onTouchEvent方法
(5)子View可以通過調(diào)用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup對(duì)其MOVE或者UP事件進(jìn)行攔截鸟蜡;
(6)父View可以通過onInterceptTouchEvent來攔截事件,但是如果父view不攔截down事件血淌,子view如果調(diào)用requestDisallowInterceptTouchEvent方法矩欠,那么即使父View在move和up的時(shí)候return true财剖,也不會(huì)將事件攔截掉悠夯,也只會(huì)調(diào)用子view的onTouchEvent;當(dāng)面對(duì)滑動(dòng)沖突時(shí)躺坟,我們可以考慮通過requestDisallowInterceptTouchEvent設(shè)置FLAG_DISALLOW_INTERCEPT標(biāo)志位來解決滑動(dòng)沖突沦补;如果父View攔截Down事件,那么子View將不會(huì)收到事件咪橙;
(7)滑動(dòng)沖突的理論基礎(chǔ)是事件分發(fā)機(jī)制夕膀,所以熟悉事件分發(fā)機(jī)制有助于我們解決滑動(dòng)沖突相關(guān)問題;
以上就是事件分發(fā)機(jī)制的全部?jī)?nèi)容美侦。最后产舞,本文部分內(nèi)容直接從其他地方文章直接Copy而來,感謝本文內(nèi)容所參考文章的作者菠剩;