View與ViewGroup TouchEvent分析
ViewGroup繼承于View,而View實現(xiàn)了兩個方法 :
dispatchTouchEvent 主要用于分發(fā)事件较坛, 在不考慮父view group攔截的情況下隙券,所有touch事件首先產(chǎn)生down事件嗅绸,父view從上層獲取MotionEvent再調(diào)用子類的dispatchTouchEvent將事件傳遞給子View麻裁。
在子view的dispatchTouchEvent方法中渐夸,優(yōu)先判定是否存在touchListerner , 如果有則調(diào)用touchlisterner的touch方法嗤锉。當(dāng)返回true時,dispatchTouchEvent將返回true墓塌,并且不再執(zhí)行自己的onTouchEvent方法瘟忱。否則dispatchTouchEvent將返回子viewonTouchEvent的返回值 。
當(dāng)子view在dispatchTouchEvent的down事件里返回true之后苫幢,父view會將當(dāng)前子view設(shè)置成targetView访诱。只有target view才會收到后續(xù)的move ,up ,或者cancel事件韩肝。
說完子view触菜,再看父類的viewGroup, 父view也是由上層調(diào)用自己的dispatchTouchEvent來分發(fā)MotionEvent,事件的傳遞邏輯是先調(diào)用dispatchTouchEvent方法哀峻,然后調(diào)用onInterceptTouchEvent判斷是否攔截涡相,這里默認(rèn)是不攔截,攔截的情況后續(xù)再說剩蟀。再之后
相比view 催蝗,view group多了一個onInterceptTouchEvent方法。
該方法主要用于決定是否攔截子view獲取MotionEvent育特。默認(rèn)實現(xiàn)為false,也就是不攔截丙号。當(dāng)返回true之后,則直接將當(dāng)子view的后續(xù)點擊事件傳遞給父view.
整個流程里最復(fù)雜的部分為viewgroup的dispatchTouchEvent方法。
當(dāng)點擊事件為ACTION_DOWN的時候犬缨,首先清除當(dāng)前view group 處理點擊事件的TouchTarget鏈表喳魏,恢復(fù)view group到默認(rèn)狀態(tài)(在detach的時候也會做)。
接著判斷是否需要攔截遍尺,如果攔截則直接調(diào)用dispatchTransformedTouchEvent方法并傳入空view(當(dāng)孩子為空時默認(rèn)調(diào)用自己的super.dispatchTouchEvent方法)截酷。
當(dāng)不攔截點擊事件為ACTION_POINTER_DOWN ||ACTION_POINTER_DOWN的時候涮拗,根據(jù)從子view找出符合點擊區(qū)域的孩子依次調(diào)用子view的dispatchTouchEvent方法來判斷孩子是否處理乾戏,不論是否找到都會調(diào)用dispatchTransformedTouchEvent方法。當(dāng)有子view處理的時候會生成一個TouchTarget并加入到鏈表中三热。
對于其他的事件鼓择,當(dāng)TouchTarget鏈表為空時直接調(diào)用dispatchTransformedTouchEvent方法并傳入空view丟給自己處理,否則遍歷TouchTarget鏈表找到合適的子view處理就漾。
在cancel或者up事件的時候會清理鏈表呐能。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/**
* 第一步:對于ACTION_DOWN進(jìn)行處理(因為ACTION_DOWN是一系列事件的開端)
*
* 1. 清除以往的Touch狀態(tài)(state)開始新的手勢(gesture)
* cancelAndClearTouchTargets(ev)中有一個非常重要的操作:
* 將mFirstTouchTarget設(shè)置為null!!!!
* 2. 隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識
*/
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
cancelAndClearTouchTargets(ev);//取消和清除所有的touch target
resetTouchState();//重置所有touch狀況,準(zhǔn)備新的touch狀況周期
}
/**
* 第二步:檢查是否要攔截(Check for interception)
* 在哪些情況下會調(diào)用該代碼呢抑堡?有如下幾種情況
* 1 處理ACTION_DOWN事件時
* 2 當(dāng)ACTION_DOWN事件被子View消費后處理ACTION_MOVE和ACTION_UP時
* 因為此時mFirstTouchTarget!=null摆出。所以此時ViewGroup
* 是有機(jī)會攔截ACTION_MOVE和ACTION_UP的,但是我們也可以調(diào)用方法:
* requestDisallowInterceptTouchEvent來禁止ViewGroup的事件攔截.
* 如果子View沒有消費Touch事件,那么那么當(dāng)后續(xù)的ACTION_MOVE和ACTION_UP
* 到來時是不會調(diào)用到本處代碼的.
*
* ViewGroup.dispatchTouchEven中,使用變量intercepted來標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
* 該變量在后續(xù)代碼中起著很重要的作用.
*
* 從此處if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)及其內(nèi)部代碼可知:
* 當(dāng)ViewGroup決定攔截事件后,那么后續(xù)的點擊事件將會默認(rèn)交給它處理,不再調(diào)用onInterceptTouchEvent()
* 因為在處理ACTION_DOWN時如果Touch事件被子View消費,那么mFirstTouchTarget不為空;
* 反之,如果Touch事件沒有被子View消費,那么mFirstTouchTarget為空,即此時Touch由當(dāng)前
* 的ViewGroup攔截首妖。此時當(dāng)ACTION_MOVE和ACTION_UP來到時,不再滿足:
* if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)
* 當(dāng)然也就無法調(diào)用其內(nèi)部的onInterceptTouchEvent()偎漫。
* 通俗地說:一旦ViewGroup攔截了ACTION_DOWN事件由自身的onTouchEvent()處理,那么
* 對于后續(xù)的ACTION_MOVE和ACTION_UP而言ViewGroup不再調(diào)用onInterceptTouchEvent()
*
* 這里有個東西需要注意:FLAG_DISALLOW_INTERCEPT
* 在子View中調(diào)用requestDisallowInterceptTouchEvent()后造成disallowIntercept為true
* 即禁止攔截.于是不滿足if(!disallowIntercept)所以也就調(diào)用不到該if內(nèi)的onInterceptTouchEvent()
* 自然就沒有辦法攔截了.
* 但是requestDisallowInterceptTouchEvent()對于ACTION_DOWN是無效的.
* 因為對于ACTION_DOWN會調(diào)用 cancelAndClearTouchTargets(ev)和resetTouchState();
* 對FLAG_DISALLOW_INTERCEPT等狀態(tài)值復(fù)原重置(參考上面的代碼)
*
* 舉兩種情況說明:
* 1 當(dāng)處理ACTION_DOWN時當(dāng)然會滿足
* if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)
* 對于ACTION_DOWN子View有兩種處理結(jié)果
* 1.1 消耗了Touch事件,那么mFirstTouchTarget不為null.
* 所以處理后續(xù)的ACTION_MOVE和ACTION_UP時依然滿足該if判斷
* 1.2 沒有消耗Touch事件.mFirstTouchTarget=null.不滿足該if.
* 所以后續(xù)的ACTION_MOVE和ACTION_UP由ViewGroup處理,此時再討論什么攔截也就沒有意義了.
* 同樣的道理當(dāng)子View消費了ACTION_DOWN后當(dāng)處理ACTION_MOVE的時候ViewGroup攔截了該事件
* 那么當(dāng)ACTION_UP隨之到來時由于mFirstTouchTarget=null所以不會再調(diào)用該段代碼,自然也就
* 不會調(diào)用onInterceptTouchEvent()判斷是否攔截了.這點在上面的注釋也有提及
* 2 當(dāng)出現(xiàn)1.1的情況時滿足該if判斷.
* 如果在子View中調(diào)用了requestDisallowInterceptTouchEvent()那么就禁止攔截
* 即disallowIntercept=true.所以不滿足if (!disallowIntercept)當(dāng)然也就調(diào)用不到
* onInterceptTouchEvent(ev)了,而是執(zhí)行else{ intercepted = false;}
* 也就是說ViewGroup無法攔截Touch了.
*/
final boolean intercepted;//使用變量intercepted來標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
// 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經(jīng)找到能夠接收touch事件的目標(biāo)組件)時if成立
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//判斷disallowIntercept(禁止攔截)標(biāo)志位
//因為在其他地方可能調(diào)用了requestDisallowInterceptTouchEvent()改變該值.
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當(dāng)禁止攔截為false時(即disallowIntercept為false)調(diào)用onInterceptTouchEvent(ev)方法
if (!disallowIntercept) {
//既然disallowIntercept為false那么就調(diào)用onInterceptTouchEvent()方法將結(jié)果賦值給intercepted
//這就是常說的事件傳遞流程:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
//注意:這是dispatchTouchEvent中唯一調(diào)用onInterceptTouchEvent()的地方
//所以,只有2種情況系統(tǒng)會調(diào)用onInterceptTouchEvent:
//1.派發(fā)action_donwn有缆;2.ViewGroup里有子控件攔截或消費了action_down
//換句話說象踊,若ViewGroup攔截消費了down,那么它在接收后續(xù)的up和move時不再調(diào)用onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//禁止攔截的FLAG為ture說明沒有必要去執(zhí)行是否需要攔截了能夠順利通過,所以設(shè)置攔截變量為false
//即棚壁,當(dāng)禁止攔截為true時(即disallowIntercept為true)設(shè)置intercepted = false
intercepted = false;
}
} else {
//當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(即沒有Touch的目標(biāo)組件)時
//設(shè)置 intercepted = true表示ViewGroup執(zhí)行Touch事件攔截的操作杯矩。
//There are no touch targets and this action is not an initial down
//so this view group continues to intercept touches.
intercepted = true;
}
/**
* 第三步:檢查cancel(Check for cancelation)
*/
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
/**
* 第四步:事件分發(fā)(Update list of touch targets for pointer down, if needed)
*/
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為false(不攔截)
if (!canceled && !intercepted) { //這是intercepted體現(xiàn)作用的唯一一行代碼
//處理ACTION_DOWN事件.這個環(huán)節(jié)比較繁瑣.
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
// 依據(jù)Touch坐標(biāo)尋找子View來接收Touch事件
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 遍歷子View判斷哪個子View接受Touch事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.
// 既然已經(jīng)找到了,所以執(zhí)行break跳出for循環(huán)
// 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;
}
resetCancelNextUpFlag(child);
/**
* 如果上面的if不滿足,當(dāng)然也不會執(zhí)行break語句.
* 于是代碼會執(zhí)行到這里來.
*
*
* 調(diào)用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做
* 遞歸處理(也就是遍歷該子View的View樹)
* 該方法很重要,看一下源碼中關(guān)于該方法的描述:
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
* 將Touch事件傳遞給特定的子View.
* 該方法十分重要!!!!!!!!!!!!!!!!!
* 在該方法中為一個遞歸調(diào)用,會遞歸調(diào)用dispatchTouchEvent()方法!!!!!!!!!!
* 在dispatchTouchEvent()中:
* 如果子View為ViewGroup并且Touch沒有被攔截那么遞歸調(diào)用dispatchTouchEvent()
* 如果子View為View那么就會調(diào)用其onTouchEvent(),這個不再贅述.
*
*
* 該方法返回true則表示子View消費掉該事件,同時進(jìn)入該if判斷.
* 滿足if語句后重要的操作有:
* 1 給newTouchTarget賦值
* 2 給alreadyDispatchedToNewTouchTarget賦值為true.
* 看這個比較長的英語名字也可知其含義:已經(jīng)將Touch派發(fā)給新的TouchTarget
* 3 執(zhí)行break.
* 因為該for循環(huán)遍歷子View判斷哪個子View接受Touch事件,既然已經(jīng)找到了
* 那么就跳出該for循環(huán).
* 4 注意:
* 如果dispatchTransformedTouchEvent()返回false即子View的onTouchEvent返回false
* (即Touch事件未被消費)那么就不滿足該if條件.所以也就無法執(zhí)行addTouchTarget().
* 在此簡單說一下addTouchTarget()中涉及到的ViewGroup的一個內(nèi)部類TouchTarget——它是一個事件鏈.
* 該處的mFirstTouchTarget就是一個TouchTarget.它保存了可以消耗Touch事件的View.
* 在該處,如果dispatchTransformedTouchEvent()返回true即子View的onTouchEvent返回true則說明
* 該View消耗了Touch事件,那么將該View加入到事件鏈中!!!!!!!!!!!!!!!
* 尤其注意:
* 這個操作是在處理ACTION_DOWN的代碼塊里進(jìn)行的.即是在:
* if (actionMasked == MotionEvent.ACTION_DOWN||
* (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) ||
* actionMasked == MotionEvent.ACTION_HOVER_MOVE)
* 這個大的if判斷中處理的.
* 當(dāng)處理ACTION_MOVE事件和ACTION_UP事件的時候是不會進(jìn)入這個if判斷的!!!!!
* 而是直接從去判斷mFirstTouchTarget!!!!!!!!!!!!!!!!
* 所以如果一個View不處理ACTION_DOWN那么該,那么該View是不會保存在mFirstTouchTarget
* 中的,也就無法繼續(xù)處理ACTION_MOVE事件和ACTION_UP事件!!!!!!!!!!即若該View不消耗
* ACTION_DOWN事件那么系統(tǒng)是不會講ACTION_MOVE和ACTION_UP事件傳給給該View的
* 5 注意:
* 如果dispatchTransformedTouchEvent()返回true即子View
* 的onTouchEvent返回true(即Touch事件被消費)那么就滿足該if條件.
* 從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!!
* 6 小結(jié):
* 對于此處ACTION_DOWN的處理具體體現(xiàn)在dispatchTransformedTouchEvent()
* 該方法返回boolean,如下:
* true---->事件被消費----->mFirstTouchTarget!=null
* false--->事件未被消費--->mFirstTouchTarget==null
* 因為在dispatchTransformedTouchEvent()會調(diào)用遞歸調(diào)用dispatchTouchEvent()和onTouchEvent()
* 所以dispatchTransformedTouchEvent()的返回值實際上是由onTouchEvent()決定的.
*
* 簡單地說onTouchEvent()是否消費了Touch事件(true or false)的返回值決定了
* dispatchTransformedTouchEvent()的返回值!!!!從而決定了mFirstTouchTarget是否為null!!!!!!
* 從而進(jìn)一步?jīng)Q定了ViewGroup是否處理Touch事件.這一點在下面的代碼中很有體現(xiàn).
*/
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//調(diào)用addTouchTarget()將child添加到mFirstTouchTarget鏈表的表頭
//注意在addTouchTarget()方法內(nèi)部會對mFirstTouchTarget操作,使其不為null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}//for循環(huán)結(jié)束
}
/**
* 該if條件表示:
* 經(jīng)過前面的for循環(huán)沒有找到子View接收Touch事件并且之前的mFirstTouchTarget不為空
*/
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指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}//處理ACTION_DOWN結(jié)束
}
}
/**
* 經(jīng)過上面對于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況:
* (當(dāng)然如果不是ACTION_DOWN就不會經(jīng)過上面較繁瑣的流程而是從此處開始執(zhí)行,比如ACTION_MOVE和ACTION_UP)
*
* 情況1 mFirstTouchTarget為null
* 即沒有找到能夠消費touch事件的子組件或者是touch事件被攔截了
* 情況2 mFirstTouchTarget不為null
* 即找到了能夠消費touch事件的子組件則后續(xù)的touch事件都可以傳遞到子View
* 這兩種情況的詳細(xì)分析見下.
*
* 這兩種情況下都會去調(diào)用方法:
* dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits)
* 我們重點關(guān)注該方法的第三個參數(shù)View child.
* 詳情請參加下面dispatchTransformedTouchEvent()源碼分析
* 在該源碼中解釋了:
* 為什么子view對于Touch事件處理返回true那么其上層的ViewGroup就無法處理Touch事件了!!!!!!!!!
* 為什么子view對于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件!!!!!!!!!!
*
*/
if (mFirstTouchTarget == null) {
/**
* 情況1:mFirstTouchTarget為null
*
* 經(jīng)過上面的分析mFirstTouchTarget為null就是說Touch事件未被消費.
* 即沒有找到能夠消費touch事件的子組件或Touch事件被攔截了,
* 則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
* 即子View沒有消費Touch事件,那么子View的上層ViewGroup才會調(diào)用其onTouchEvent()處理Touch事件.
* 在源碼中的注釋為:No touch targets so treat this as an ordinary view.
* 也就是說此時ViewGroup像一個普通的View那樣調(diào)用dispatchTouchEvent(),且在dispatchTouchEvent()
* 中會去調(diào)用onTouchEvent()方法.
* 具體的說就是在調(diào)用dispatchTransformedTouchEvent()時第三個參數(shù)為null.
* 第三個參數(shù)View child為null會做什么樣的處理呢?
* 請參見下面dispatchTransformedTouchEvent()的源碼分析
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
/**
* 情況2:mFirstTouchTarget不為null
* 即找到了可以消費Touch事件的子View且后續(xù)Touch事件可以傳遞到該子View
* 在源碼中的注釋為:
* 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) {
//如果前面利用ACTION_DOWN事件尋找符合接收條件的子組件的同時消費掉了ACTION_DOWN事件
//那么這里為handled賦值為true
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
//對于非ACTION_DOWN事件繼續(xù)傳遞給目標(biāo)子組件進(jìn)行處理
//依然是遞歸調(diào)用dispatchTransformedTouchEvent()
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;
}
}
/**
* 處理ACTION_UP和ACTION_CANCEL
* Update list of touch targets for pointer up or cancel, if needed.
* 在此主要的操作是還原狀態(tài)
*/
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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
//=====================以上為dispatchTouchEvent()源碼分析======================
//===============以下為dispatchTransformedTouchEvent()源碼分析=================
/**
* 在dispatchTouchEvent()中會調(diào)用dispatchTransformedTouchEvent()將事件分發(fā)給子View處理
*
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*
* 在此請著重注意第三個參數(shù):View child
* 在dispatchTouchEvent()中多次調(diào)用dispatchTransformedTouchEvent(),但是有時候第三個參數(shù)為null,有時又不是.
* 那么這個參數(shù)是否為null有什么區(qū)別呢袖外?
* 在如下dispatchTransformedTouchEvent()源碼中可見多次對于child是否為null的判斷,并且均做出如下類似的操作:
* if (child == null) {
* handled = super.dispatchTouchEvent(event);
* } else {
* handled = child.dispatchTouchEvent(event);
* }
* 這個代碼是什么意思呢?
*
* 當(dāng)child == null時會將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
* 即super.dispatchTouchEvent(event)
* 正如源碼中的注釋描述的一樣:
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*
* 當(dāng)child != null時會調(diào)用該子view(當(dāng)然該view可能是一個View也可能是一個ViewGroup)的dispatchTouchEvent(event)處理.
* 即child.dispatchTouchEvent(event);
*
* 那么該child是否為null又是由什么決定的呢史隆?
* 在dispatchTouchEvent()中已經(jīng)知道了:
* 如果Touch事件被消耗掉那么child不為null
* 如果Touch事件未被消耗掉那么child為null
*
* 這就解釋了:
* 為什么子view對于Touch事件處理返回true那么其上層的ViewGroup就無法處理Touch事件了!!!!!!!!!
* 為什么子view對于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件!!!!!!!!!!
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//調(diào)用自身的dispatchTouchEvent()!!!!!!
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
//調(diào)用子View的dispatchTouchEvent()!!!!!!
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 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);
}
transformedEvent.recycle();
return handled;
}
//===============以上為dispatchTransformedTouchEvent()源碼分析=================
}