觸屏是用戶和手機(jī)交互的基礎(chǔ),手指觸屏?xí)r產(chǎn)生一系列事件边败,控制視圖改變业扒,在樹形視圖中,事件從頂層向下傳遞酣藻。
View和ViewGroup的dispatchTouchEvent方法曹洽,事件傳遞到視圖的第一個方法,它們實現(xiàn)方式不同辽剧,ViewGroup容器視圖送淆,要么消費掉事件,要么派發(fā)給某個子視圖怕轿。View非容器視圖偷崩,自己接手處理。ViewGroup的dispatchTouchEvent方法撞羽。
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//第一部分阐斜,down事件初始化
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//第二部分,檢查攔截
//第三部分诀紊,非打斷谒出,非取消時處理,代碼段貼在后面渡紫,遍歷子視圖
//第四部分到推,代碼段貼在后面,發(fā)到touch目標(biāo)
//第五部分惕澎,最終處理莉测。
}
...
return handled;
}
事件的初始化
down事件,表示手指第一次接觸到屏幕唧喉,清除以前保存的TouchTargets鏈表捣卤,鏈表保存上一次觸屏接收事件的子視圖。
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
...
//發(fā)送ACTION_CANCEL事件
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
//清空TouchTarget八孝,鏈表每一項元素recycle回收
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
清理上一次觸摸遺留下來的東西董朝。
攔截判斷
下面是摘取的相關(guān)代碼段。
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
//重設(shè)action放置改變干跛。
ev.setAction(action);
} else {
intercepted = false;//設(shè)置過標(biāo)志子姜,永遠(yuǎn)不攔截
}
} else {
intercepted = true;
}
...
// 是否ACTION_CANCEL類型
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
down事件和mFirstTouchTarget鏈表不空,這兩種情況需要攔截判斷楼入。
down是第一個事件哥捕,需要攔截判斷牧抽。
非down事件時,鏈表mFirstTouchTarget不空遥赚,說明前面已經(jīng)存在接收事件到目標(biāo)子視圖扬舒,(或許不止一個),可以直接派發(fā)到目標(biāo)凫佛,要經(jīng)過一層攔截判斷讲坎。
這兩種情況進(jìn)行攔截判斷是合理的。
不滿足以上兩個條件愧薛,說明在down事件時晨炕,子視圖中不存在可接收消費事件到目標(biāo),對于非down事件毫炉,不需要向子視圖派發(fā)府瞄,也不需攔截判斷,直接設(shè)置intercepted標(biāo)志碘箍,交給容器視圖onTouchEvent方法。
遍歷查找滿足條件子視圖
經(jīng)過一次攔截判斷鲸郊,不攔截且不取消事件類型時丰榴,優(yōu)先向子視圖派發(fā),事件類型必須是down或pointer_down秆撮,才會向子視圖中查找目標(biāo)四濒。
move事件不會走這一步,會直接派發(fā)給已存在的mFirstTouchTarget目標(biāo)职辨,無目標(biāo)就自己處理盗蟆,不需要在子視圖查找,有intercepted標(biāo)志舒裤,不會來這里喳资。
...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {//非打斷,非取消處理
...
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // down事件總是0
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//刪除該pointerId曾經(jīng)存儲在某個TouchTarget的記錄腾供。
//因pointerId重新觸摸仆邓,并確定將被哪個子視圖處理。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 找到可以接收事件的View伴鳖,從前向后掃描查找
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
... //觸摸點坐標(biāo)(x,y)區(qū)域范圍判斷
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//找到newTouchTarget退出遍歷
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//未找到newTouchTarget节值,繼續(xù)
resetCancelNextUpFlag(child);
//看子視圖是否消費。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
if (preorderedList != null) preorderedList.clear();
}
//未處理的pointerId分配給現(xiàn)有TouchTarget
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 新手指未找到可接受事件的View
// 將idBitsToAssign分配到最早手指的目標(biāo)
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
查找一個可以接收事件的子視圖榜聂,創(chuàng)建一個TouchTarget對象搞疗,加入鏈表,如果該視圖已存在TouchTarget,說明是pointer_down類型事件猛拴,已有一個手指觸摸在該視圖,將pointerId合并到該TouchTarget谒获,多個手指觸屏到該子視圖扳埂。
子視圖數(shù)組遍歷順序业簿,如果設(shè)置Z軸值,preorderedList不是空阳懂,按照Z軸排序的列表梅尤,立體的Z軸越大,優(yōu)先分發(fā)岩调,一般情況下不設(shè)置巷燥。從子視圖數(shù)組mChildren尾部開始,按照從大到小的索引遍歷号枕,針對可能重疊放置的子視圖缰揪,保證最上面,也就是最后加入的先接收事件葱淳。數(shù)組索引是xml中定義的索引钝腺,其中,setChildrenDrawingOrderEnabled方法赞厕,可以控制子視圖繪制順序艳狐,getChildDrawingOrder方法,可以獲取該順序皿桑,一般情況下毫目,繪制順序childIndex與數(shù)組索引相同,復(fù)雜情況下诲侮,設(shè)置了setChildrenDrawingOrderEnable(boolean)镀虐,并重寫ViewGroup的getChildDrawingOrder方法,(默認(rèn)的是按照子視圖的添加順序沟绪,即視圖數(shù)組的索引順序)刮便,改變子視圖繪制順序,則子視圖索引childIndex就與遍歷當(dāng)前i值不同近零。
通過兩個方法判斷子視圖滿足事件接收的條件诺核。
private static boolean canViewReceivePointerEvents(View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
視圖可見或有動畫。
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
判斷觸摸點坐標(biāo)(x,y)是否位于子視圖區(qū)域范圍久信。根據(jù)MotionEvent的getX和getY方法窖杀,獲取觸控點坐標(biāo)。
區(qū)別getX和getRawX裙士。
getX是相對父視圖坐標(biāo)系的坐標(biāo)值入客,getRawX是相對整個屏幕坐標(biāo)系的坐標(biāo)值。
以父容器坐標(biāo)系為標(biāo)準(zhǔn)。ViewGroup的transformPointToViewLocal方法桌硫,將觸控點坐標(biāo)夭咬,轉(zhuǎn)化成相對子視圖坐標(biāo)系的坐標(biāo)值,減去子視圖相對父視圖mLeft/mTop距離就能實現(xiàn)轉(zhuǎn)換铆隘,若父視圖存在Scroll卓舵,再加上Scroll。
View的pointInView方法膀钠,判斷轉(zhuǎn)換后的坐標(biāo)值是否在子視圖(0,0,width,heigh)區(qū)域范圍掏湾,在該范圍內(nèi)說明觸摸點在該子視圖內(nèi)部。
兩個條件同時滿足肿嘲,找到子視圖融击。遍歷TouchTargets鏈表,查找與該子視圖對應(yīng)的TouchTarget雳窟。
查找到newTouchTarget目標(biāo)
說明pointer_down類型事件尊浪,第2個甚至3、4...個手指觸摸坐標(biāo)均在該子視圖中封救,將手指pointId合并到目標(biāo)Target的pointerIdBits拇涤,結(jié)束遍歷,下面不用再執(zhí)行dispatchTransformedTouchEvent方法去查看子視圖是否消費了誉结,因為已經(jīng)有TouchTarget綁定了該pointerId工育。
未查到newTouchTarget目標(biāo)。
可能存在兩種情況搓彻,down事件,已經(jīng)清理過鏈表嘱朽,pointer_down事件旭贬,新手指觸摸子視圖與前一手指正在觸摸子視圖不同。
將繼續(xù)執(zhí)行dispatchTransformedTouchEvent方法搪泳。
將事件傳遞給子視圖稀轨,成功消費后,新建newTouchTarget岸军,插入鏈表頭部奋刽,宣告down/pointer_down事件被子視圖成功消費,結(jié)束遍歷艰赞,不必再查找其他子視圖啦佣谐。
最后,將未處理的pointerId分配給現(xiàn)有TouchTarget方妖。出現(xiàn)這種情況的場景狭魂。
down事件,如果分發(fā)子視圖成功,會新建newTouchTarget目標(biāo)雌澄,且同時賦值mFirstTouchTarget斋泄,若分發(fā)子視圖失敗,二者都是空镐牺,down事件不會出現(xiàn)這種情況炫掐。
pointer_down事件,第一觸控點在該子視圖的一個兄弟視圖上睬涧,第二觸控點在該子視圖分發(fā)失敗或并未觸摸到任何子視圖募胃,均會導(dǎo)致newTouchTarget是空。說明pointer_down事件未被任何一個子視圖成功消費宙地。idBitsToAssign合并到鏈表最后一項元素的pointerIdBits中摔认。
將未被消費的手指pointerId合并到另一個手指的消費目標(biāo)中,之前已有多個手指觸摸的話宅粥,合并到最早創(chuàng)建的那個TouchTarget目標(biāo)参袱,在鏈表尾部。合并圖秽梅。
圖中三個TouchTarget抹蚀,三個目標(biāo)視圖,四個觸控點企垦,pointer_down事件成功派發(fā)子視圖的TouchTarget插入鏈表前端环壤,pointId代表pointer_down事件產(chǎn)生的手指觸控點Id。如圖钞诡,pointerId是3的觸控點未找到合適的視圖郑现,合并到TouchTarget3中(紅色)。
綜上所述
容器視圖派發(fā)荧降,經(jīng)歷事件初始化接箫,攔截判斷,子視圖遍歷查找朵诫。
攔截判斷辛友,down事件或目標(biāo)鏈存在。
單個手指第一次觸摸時剪返,才會找到觸摸子視圖废累,看他能否承接消費事件,可以才為其創(chuàng)建TouchTarget脱盲。
系統(tǒng)自動為未消費的觸點分配目標(biāo)邑滨,前提是目標(biāo)鏈存在。
目標(biāo)處理
if (mFirstTouchTarget == null) {
//自身處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//子視圖傳遞
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;//該手指第一次觸屏被處理了钱反,是新目標(biāo)驼修。
} else {//已有目標(biāo)殿遂。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//根據(jù)目標(biāo)pointerIdBits匹配手指
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
//若是打斷,將目標(biāo)target回收乙各。
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
//設(shè)置前一個處理目標(biāo)墨礁。
predecessor = target;
target = next;
}
}
鏈表是空,可以推斷出耳峦,無子視圖消費down事件恩静。未找到符合觸摸坐標(biāo)的子視圖或者找到子視圖但未消費。
調(diào)用dispatchTransformedTouchEvent方法蹲坷,子視圖參數(shù)傳空驶乾,交給基類View的dispatchTouchEvent方法,把當(dāng)前容器視圖當(dāng)成一個View視圖循签,View的dispatchTouchEvent方法將觸發(fā)onTouchEvent方法级乐,觸控點id傳ALL_POINTER_IDS。
鏈表不是空县匠,遍歷风科,說明至少存在一個目標(biāo)視圖消費事件,多個節(jié)點說明多個手指觸控點存在子視圖消費事件乞旦。
如果有新增標(biāo)志且目標(biāo)是newTouchTarget贼穆,說明事件是pointer_down或down類型。在前面代碼中兰粉,事件已dispatchTransformedTouchEvent被子視圖消費掉故痊,直接設(shè)置handled標(biāo)志。簡單情況下玖姑,只有一個手指觸摸愕秫,一個目標(biāo),可以將直接handled返回焰络。
如果非當(dāng)前新增豫领,該事件有任何類型的可能,派發(fā)事件到該目標(biāo)的對應(yīng)子視圖舔琅,交給dispatchTransformedTouchEvent處理,根據(jù)處理結(jié)果設(shè)置handled標(biāo)志洲劣。
在onInterceptTouchEvent方法被攔截备蚓,設(shè)置cancelChild標(biāo)志,處理時囱稽,向子視圖發(fā)送cancel事件郊尝,交給容器視圖處理。子視圖在move事件正常消費過程中战惊,突然遭遇容器視圖攔截流昏,傳遞給子視圖的事件改變?yōu)閏ancel事件,這次事件子視圖的返回依然是消費成功。鏈表所有元素依次被回收况凉。下一次move事件再次判斷時谚鄙,表頭mFirstTouchTarget是空,代碼不再執(zhí)行到這里刁绒,事件也不會向下傳遞闷营。
設(shè)置cancelChild標(biāo)志子視圖,從鏈表刪除這個元素知市。前一個元素predecessor引用傻盟,遍歷鏈表刪除元素時,可以找到前面的引用嫂丙,將其next指向next娘赴,刪除當(dāng)前。
如果未被攔截跟啤,
不設(shè)置cancelChild標(biāo)志诽表。子視圖分發(fā)處理。以上情況腥光。該手指觸屏事件有可能是各種類型关顷,鏈表元素也可能有多個。那么武福,一個手指的在某個子視圖的事件都會經(jīng)歷整個目標(biāo)鏈议双,都會派發(fā)么?在真正派發(fā)方法中再分析捉片。
派發(fā)的最終處理
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);
}
cancel和up事件類型平痰,表示事件結(jié)束,清空內(nèi)部目標(biāo)鏈表伍纫,不會再有事件傳遞到該視圖宗雇,pointer_up事件,表示有一個手指觸控點離開莹规,仍有其他手指觸控點赔蒲,將該觸控點對應(yīng)pointerId在TouchTarget中清除。
真正的派發(fā)方法
dispatchTransformedTouchEvent方法良漱,在前面第三和四部分都涉及過該方法舞虱,分別是新接觸點消費和遍歷目標(biāo)鏈表消費。
private boolean dispatchTransformedTouchEvent(MotionEvent event,
boolean cancel,View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//派發(fā)給子視圖或當(dāng)前視圖父類即View處理母市。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//oldPointerIdBits矾兜,所有手指pointerId集合
//desiredPointerIdBits,目標(biāo)觸控點集合
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//目標(biāo)里面一個當(dāng)前的手指pointerId都沒有患久,說明該id離開了屏幕
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
//匹配成功
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
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;
}
有幾個參數(shù)椅寺,事件對象浑槽,取消標(biāo)志,目標(biāo)視圖以及目標(biāo)觸控點返帕,在每一個TouchTarget中桐玻,都保存pointerIdBits,代表觸控點位溉旋。
如果有cancel標(biāo)志畸冲,或者是cancel事件,
將MotionEvent對象設(shè)置為cancel事件派發(fā)观腊,派發(fā)給子視圖或當(dāng)前視圖邑闲。表示這層視圖有打斷,或者是更上層視圖有打斷梧油,發(fā)給該層視圖的cancel事件苫耸。
根據(jù)MotionEvent對象,獲取觸控點pointerId儡陨,
因為在上面代碼中褪子,每個事件都會經(jīng)歷整個目標(biāo)鏈表,需要將該事件觸控點匹配目標(biāo)內(nèi)部存儲集合骗村,只能向包含該pointerId目標(biāo)TouchTarget的子視圖派發(fā)嫌褪。desiredPointerIdBits表示合并到處理目標(biāo)TouchTarget的觸控點集合。
oldPointerIdBits是所有手指觸控點集合胚股,
將它與處理目標(biāo)的觸控點集合與操作笼痛,如果新值是0,沒有位相等琅拌,說明該目標(biāo)的觸控id集合對應(yīng)的手指已經(jīng)不再屏幕缨伊,無法匹配。
如果newPointerIdBits與oldPointerIdBits相等进宝,表示很可能是所有的手指均觸控在目標(biāo)觸控點對應(yīng)子視圖上刻坊。
如果不相等,從所有手指觸控點id中党晋,分離出desiredPointerIdBits中存儲的pointerId谭胚,然后封裝成一個新的事件transformedEvent,分發(fā)到目標(biāo)對應(yīng)子視圖未玻。這里再看一下第四部分的那個問題灾而。
舉例,視圖有兩個子視圖深胳,各有1個手指觸摸,目標(biāo)鏈有兩個TouchTarget铜犬,通過移動其中1個手指產(chǎn)生move事件舞终,按照前面的邏輯轻庆,事件會經(jīng)歷整個目標(biāo)鏈,兩個TouchTarget都會處理敛劝,執(zhí)行兩次dispatchTransformedTouchEvent方法余爆。從2個手指分離出每一個子視圖觸控點集合,封裝事件夸盟,然后發(fā)向每一個子視圖蛾方,只有一個手指move,兩個子視圖都會收到move事件上陕。
定義了兩個View桩砰,兩個手指分別觸摸他們,一個手指不動释簿,滑動另外一個亚隅,產(chǎn)生連續(xù)move事件,結(jié)果兩個視圖都會接收到庶溶,而且是前后連續(xù)的煮纵,說明事件是先后發(fā)送兩個視圖的,就是在遍歷TouchTarget時偏螺。后面的數(shù)字是打印View對象的HashCode行疏。
如果child是空時,父類分發(fā)套像,child不是空時酿联,子視圖分發(fā)。
若子視圖是容器凉夯,處理方法與前面一致货葬,每層樹結(jié)構(gòu)節(jié)點ViewGroup的dispatchTouchEvent方法遞歸,若子視圖是葉子節(jié)點劲够,觸發(fā)基類View的dispatchTouchEvent方法震桶。
派發(fā)給子視圖或本視圖父類View#dispatchTouchEvent。關(guān)鍵點是入?yún)⒆右晥D是否為空征绎。不管是以上哪一種情況蹲姐,事件派發(fā)成功返回true標(biāo)志。
到這里人柿,ViewGroup的dispatchTouchEvent方法的這六個部分分析完了柴墩。
單手指觸控流程
TouchTarget鏈表中僅有一個節(jié)點。
down事件凫岖,觸控點位于子視圖江咳,且子視圖消費,創(chuàng)建TouchTarget哥放,封裝子視圖與觸控點pointerId歼指,子視圖未消費爹土,TouchTarget一直保持空,ViewGroup自己處理踩身。
move事件胀茵,TouchTarget不空,說明down事件已找到合適的消費目標(biāo)挟阻,交給TouchTarget內(nèi)部保存的子視圖處理琼娘,TouchTarget是空,ViewGroup自己處理附鸽。
down脱拼,move,up事件來到ViewGroup拒炎,第一站是dispatchTouchEvent方法挪拟。
- down先來,清理遺留TouchTarget击你,onInterceptTouchEvent決定是否打斷玉组,兩種處理方式。子視圖成功處理時給mFirstTouchTarget賦值TouchTarget對象丁侄。
- move進(jìn)來惯雳,看mFirstTouchTarget有值么,沒有鸿摇?子視圖不給力石景,down未搞定,必須打斷自己處理拙吉。mFirstTouchTarget有值潮孽,子視圖已經(jīng)搞定down,onInterceptTouchEvent決定是否打斷筷黔,打斷向子視圖發(fā)Cancel往史,置空TouchTarget,繼續(xù)走子視圖處理佛舱,下一次move事件則走另一條TouchTarget為空的線路椎例。
若不斷的有move事件進(jìn)來則說明自己本身View#dispatchTouchEvent或者子視圖一定成功處理,包括Down事件请祖。
只有ViewGroup#dispatchTouchEvent從Down事件開始向上層返回true订歪,才會在上層ViewGroup中為其建立一個TouchTarget,對上層視圖來說肆捕,該ViewGroup消費了事件才會有源源不斷的move事件進(jìn)來刷晋。 - 從上層向下看,該ViewGroup#dispatchTouchEvent返回true,說明在當(dāng)前ViewGroup中事件被處消化眼虱,至于是它的子視圖還是本身消化的或舞,上層不關(guān)心,只要求結(jié)果蒙幻。返回false,對上層來說該ViewGroup總歸是沒消化胆筒。
總結(jié)
單個手指從觸摸到離開屏幕產(chǎn)生的完整事件流ACTION_DOWN邮破、一系列ACTION_MOVE、ACTION_UP仆救。
進(jìn)入視圖的每個事件抒和,先交給dispatchTouchEvent方法分發(fā),本視圖或其子視圖消費事件彤蔽,View和ViewGroup的分發(fā)方案不同摧莽,ViewGroup重點是子視圖分發(fā),View重點是本視圖消費顿痪。
攔截镊辕,一旦攔截成功,即使前期事件有處理目標(biāo)蚁袭,也會將目標(biāo)回收征懈,后續(xù)事件不會再觸發(fā)攔截方法,View類無攔截方法揩悄。
視圖處理卖哎,onTouchEvent方法,ViewGroup視圖删性,攔截或子視圖未消費時調(diào)用亏娜,View視圖,無Touch監(jiān)聽器時調(diào)用蹬挺,當(dāng)前視圖及子視圖的最后一道防線维贺,如果onTouchEvent未消費,上層便不會為該子視圖保存目標(biāo)汗侵,后續(xù)事件再無法向它傳遞了幸缕。
一個子視圖成功處理down事件,父視圖內(nèi)部將為其創(chuàng)建目標(biāo)晰韵,綁定該視圖发乔,不攔截情況下,后續(xù)的move事件將直接傳遞到該視圖處理雪猪。未成功處理down事件栏尚,將down事件交給父視圖onTouchEvent方法處理,其他事件再也不會傳遞到該視圖只恨。
一個子視圖成功處理down事件译仗,父視圖內(nèi)部將為其創(chuàng)建目標(biāo)抬虽,綁定該視圖,不攔截情況下move事件直接傳遞到該視圖處理纵菌,如果move事件未成功處理阐污,事件將無視圖接手,包括父視圖的onTouchEvent方法咱圆,最終將交給Activity的onTouchEvent處理笛辟。
任重而道遠(yuǎn)