??之前分析了一下Android中的消息傳遞機制,不知道對各位有沒有幫助!哈哈,別怪我寫的太垃圾了......也不要說的太多的廢話了,直接進入今天的主題--Android 事件分發(fā)機制婶溯。還是那樣,文章如有錯誤,請各位指正迄委,本文參考資料:
??1.任玉剛老師的《Android 開發(fā)藝術探索》
??2.徐宜生老師的《Android 群英傳》
??注意褐筛,本文的所有代碼都是 API 26,如果是其他的版本叙身,會做特別說明渔扎!
1.概述
??我們還是繼承一下《Android 消息處理機制》的格式,先來概述一下今天的內容信轿,假裝符合面向對象的繼承特性晃痴。。财忽。
??在事件傳遞機制中倘核,必須講解的三個方法:
??1.public boolean dispatchTouchEvent(MotionEvent ev)方法,這個方法作用主要是用來分發(fā)事件即彪。也就是說紧唱,當一個事件傳遞當前View的dispatchTouchEvent方法里面,這個方法可以決定將事件分發(fā)到哪里去隶校,這里的分發(fā)到哪里去表示有兩個意思:1.將事件分發(fā)到子View(如果有子View的話)漏益;2.將事件分發(fā)到分發(fā)到自己的onTouchEvent方法里面去消耗。
??2.public boolean onInterceptTouchEvent(MotionEvent ev)方法惠况,這個方法的作用是用來決定當前的View或者ViewGroup是否攔截這個事件遭庶,如果返回true的話,那么就表示攔截稠屠;反之,表示不攔截翎苫。前排預警一下权埠,這個方法有很多的坑,不是返回一個true或者false那么簡單煎谍。
??3.public boolean onTouchEvent(MotionEvent event)方法攘蔽,這個方法是具體消耗事件的方法,如果返回true的話呐粘,表示當前的View已經將這個事件消耗了满俗。
??可能大家看我寫了這些,還是覺得一臉懵逼作岖。這三個方法的意思大家都懂唆垃,說這些有什么用。大哥痘儡,不要急辕万,我們來慢慢的分析。
??當前一個事件發(fā)生了,事件傳遞的流程是從上層依次傳遞到下層渐尿,直到這個事件被處理醉途,例如:
??上圖中,當在事件發(fā)生點發(fā)生了事件砖茸,它的傳遞順序是:ViewGroupA ->ViewGroupB ->View隘擎。然后我們在結合上面的三個方法來更加形象的展示一下,事件分發(fā)的順序:
??這里從圖中可以看出來凉夯,事件是從ViewGroupA開始的嵌屎,先調用A的dispatchTouchEvent方法,進行分發(fā)恍涂,同時還會調用A的onInterceptTouchEvent方法宝惰,如果onInterceptTouchEvent方法返回的是false,表示ViewGroupA不攔截此事件再沧,于是將事件傳遞給ViewGroupB尼夺,ViewGroupB也進行跟ViewGroupA一樣的操作。如果ViewGroupB也不進行攔截的話炒瘸,那么首先就會傳遞到View的dispatchTouchEvent方法淤堵,由于View再沒有子View了,所以不能進行向下分發(fā)顷扩,所以只能傳遞到View的onTouchEvent方法里面來拐邪。如果View消耗了這個事件的話,那么這個事件傳遞的流程就在這里結束隘截,不會繼續(xù)將事件傳到ViewGroupB的onTouchEvent方法里面去扎阶;反之如果View不消耗這個事件的話,那么就繼續(xù)往上傳遞婶芭。
??上面只分析了ViewGroup不對事件進行攔截的情況东臀,下面來分析一下當一個ViewGroup攔截了事件的情況。例如:
??一旦犀农,ViewGroupA對事件進行攔截惰赋,直接將事件傳遞給ViewGroupA的onTouchEvent方法里面去。
??這個大的流程差不多就是這樣的呵哨,可能中間有非常多的細節(jié)沒有提及到赁濒,但是不急,待會的源碼分析有你們好受的C虾Α>苎住!哈哈纹坐,開玩笑枝冀!
2.ViewGroup的事件分發(fā)
(1).DecorView
??當我們用手指在屏幕點擊時舞丛,事件首先被傳遞到Activity的dispatchTouchEvent方法。對的哦果漾!你沒有看錯球切,Activity也有dispatchTouchEvent方法。我們來看看Activity的dispatchTouchEvent方法代碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
??可見绒障,當Activity的dispatchTouchEvent方法接收到了一個事件之后吨凑,Activity會將這個傳遞到Window里面去,我們再去看看:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
??哦豁户辱,我們發(fā)現superDispatchTouchEvent所在的Window類是一個抽象類鸵钝,怎么辦?不急庐镐,在Window類解釋中恩商,google爸爸給我們這么說的(代碼根據 api 26):
The only existing implementation of this abstract class is
android.view.PhoneWindow, which you should instantiate when needing a
Window.
??這里說的是,Window抽象類的唯一實現類在是android.view.PhoneWindow必逆。然后我們到PhoneWindow里面去看看相應的方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
??好嘛怠堪,又繼續(xù)跳,然后我們就到了DecorView類的superDispatchTouchEvent方法里面來了名眉。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView又是什么鬼粟矿?DecorView其實我們界面的頂級容器,也就是我們視圖樹的根损拢,是被添加到Window的陌粹。而DecorView作為頂級View,一般情況下福压,它內部會類似于LinearLayout的豎直布局掏秩,在這個布局里面有上下兩個部分,上面是標題欄隧膏,下面是Content View部分哗讥,在Activity 的setContView所設置的布局文件就是添加到Content View的部分。如圖:
??通常來說胞枕,我們可以通過如下代碼來我們自己設置的ContentView對象
ViewGroup viewGroup = getWindow().getDecorView().findViewById(android.R.id.content);
??從這里,我們知道DecorView肯定是一個ViewGroup對象魏宽,我們繼續(xù)點擊dispatchTouchEvent方法腐泻,發(fā)現進入到了ViewGroup的dispatchTouchEvent方法里面來了。
??好嘛队询,費了半天的勁派桩,我們終于看到了重頭戲了。好了好了蚌斩,我們整裝待發(fā)铆惑,準備好好的來看一下這個方法!不過我們先來總結,我們獲取了哪些信息:
??1.一個事件首先會被傳遞到Activity的dispatchTouchEvent方法里面员魏,然后最終會傳遞DecorView中去丑蛤,最后通過DecorView調用ViewGroup的dispatchTouchEvent方法來進行事件的分發(fā)。
??2.DecorView是一個Activity的根本局撕阎,實際上他也是一個ViewGroup受裹。
(2).ViewGroup對View事件的分發(fā)
??由于dispatchTouchEvent方法源代碼太多了,所以我就不像消息機制那篇文章貼出完整的代碼虏束,在這里知識貼出部分代碼來進行理解棉饶。
??首先,我們來看看這段代碼:
// Check for interception.
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
??這段代碼的作用是非常明顯的镇匀,就是check當前的ViewGroup是否需要攔截當前的事件照藻。我們發(fā)現在這段代碼里面發(fā)現了另一個比較眼熟的方法onInterceptTouchEvent方法。從代碼中汗侵,我們可以看出幸缕,ViewGroup判斷一個事件是否需要判斷實在dispatchTouchEvent方法里面對方法進行調用。
??然后我們再看看調用onInterceptTouchEvent方法的條件晃择。首先冀值,action為ACTION_DOWN的話,需要判斷當前的是否攔截宫屠,這個非常好理解列疗。但是mFirstTouchTarget是什么什么意思?實際上呢浪蹂,這個從后面的代碼邏輯中可以看出來抵栈,當ViewGroup的子元素成功處理一個事件的時候,mFirstTouchTarget會被賦值并指向該子元素坤次。換一句話說古劲,當ViewGroup不攔截事件,將事件交由給子元素來處理時缰猴,mFirstTouchTarget就不為null了产艾。也就是說,當事件序列的開始--ACTION_DOWN來到時滑绒,這時候mFirstTouchTarget是為null(因為這是第一次來闷堡,所以事件還沒有傳遞給它的子元素),如果此時ViewGroup在onInterceptTouchEvent返回為true的話疑故,表示攔截這個事件序列杠览,然后后面的ACTION_MOVE和ACTION_UP來到時,由于此時調用onInterceptTouchEvent方法的條件不符合纵势,所以onInterceptTouchEvent不會再被調用踱阿。為什么這里調用onInterceptTouchEvent方法的條件不符合呢管钳,因為第一次的down事件被ViewGroup攔截了,從而導致down事件沒有被傳遞到子View软舌,所以mFirstTouchTarget肯定為null才漆,當ACTION_MOVE和ACTION_UP兩個事件來到,actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null肯定為false的葫隙!
??從而栽烂,我們從這段里面得到一個結論,一旦一個ViewGroup在onInterceptTouchEvent方法里面對ACTION_DOWN事件進行攔截恋脚,屬于同一個事件序列的后續(xù)事件也會被攔截腺办,同時onInterceptTouchEvent方法只會被調用一次,也就是對ACTION_DOWN進行攔截的那一次糟描!
??說到這里怀喉,那么有沒有辦法對其他事件進行需求性的攔截呢?有的船响,這個問題躬拢,我們后續(xù)再講!現在就講的話见间,就不能顯示我牛逼了聊闯!哈哈,開玩笑的米诉,應該時時刻刻記住自己就是一個菜雞菱蔬!
??在剛剛的那段代碼中,我們還發(fā)現有這個判斷
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
......
}
??其中史侣,我們需要關注的是FLAG_DISALLOW_INTERCEPT 標記位拴泌,這個標記位是通過ViewGroup里面的requestDisallowInterceptTouchEvent方法來設置的,一般用于子View惊橱。一旦FLAG_DISALLOW_INTERCEPT被設置了蚪腐,也就是說,我們在子View里面調用父布局的requestDisallowInterceptTouchEvent方法税朴,那么ViewGroup將無法攔截除ACTION_DOWN以外的其他點擊事件回季。
??這里為什么時候是ACTION_DOWN以外的點擊事件呢?這是因為正林,ACTION_DOWN事件會重置FLAG_DISALLOW_INTERCEPT標記位茧跋,導致子View設置的這個標記位無效。我們來看看代碼:
// Handle an initial down.
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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
??從dispatchTouchEvent的代碼看來卓囚,上面這段代碼在我們之前那段代碼的前面,所以在ViewGroup在判斷事件是否需要攔截之前诅病,就會重置FLAG_DISALLOW_INTERCEPT哪亿,從而導致我們的子View調用requestDisallowInterceptTouchEvent方法失效粥烁!
??經過上面的代碼,如果ViewGroup不對事件進行攔截蝇棉,那么就會將這個事件分發(fā)到能夠接收到這個事件的子View讨阻。
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.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
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.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 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 (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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();
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();
}
??根據任玉剛老師在《Android 開發(fā)藝術探索》中對這段代碼的解釋,一個子View是否能夠接收到點擊事件主要由兩點來衡量:子View是否是否在播放動畫和點擊事件是否落在子View的的區(qū)域內篡殷。如果這兩個事件能夠滿足的話钝吮,那么事件就會交給它來處理厘贼。
??這里將會詳細的講解一下漆羔,事件到底是怎么傳遞到子View。ViewGroup是通過dispatchTransformedTouchEvent來將事件分發(fā)到子View的验靡!
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
??這個是事件分發(fā)代碼劲弦,其中耳标,我們會發(fā)現,如果當前子View會消耗這個事件邑跪,也就是說dispatchTransformedTouchEvent方法返回true次坡,那么將會將當前的View添加target的鏈表,而我們說的mFirstTouchTarget就是指向這個鏈表的頭画畅!這個就相當于完成的分發(fā)了嗎砸琅?
??NO!NO轴踱!沒有那么的簡單症脂,我們會發(fā)現前面有段代碼:
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 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;
}
??如果當遍歷第一個子View的時候,這里的newTouchTarget就會返回的不是null寇僧,豈不是下面的dispatchTransformedTouchEvent根本就來不及調用摊腋。像這種情況,應該怎么辦嘁傀?我們發(fā)現兴蒸,只要在這段代碼里面break,最后會執(zhí)行這段代碼:
// 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) {
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;
}
??如果說细办,之前已經將事件分發(fā)下去了橙凳,alreadyDispatchedToNewTouchTarget && target == newTouchTarget這個條件肯定為true。所以笑撞,如果在dispatchTransformedTouchEvent方法之前break岛啸,從而導致跳出循環(huán),alreadyDispatchedToNewTouchTarget肯定是為false的茴肥,因為這個變量在調用了dispatchTransformedTouchEvent方法之后會被置為true坚踩。這行代碼在之前循環(huán)遍歷子View里面。
alreadyDispatchedToNewTouchTarget = true;
??所以瓤狐,只要在之前沒有調用dispatchTransformedTouchEvent方法就break瞬铸,肯定會進入else的代碼里面∨希現在的關鍵是理解resetCancelNextUpFlag是什么意思?我們先來看看這個方法:
/**
* Resets the cancel next up flag.
* Returns true if the flag was previously set.
*/
private static boolean resetCancelNextUpFlag(@NonNull View view) {
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
??這里嗓节,我是看不懂代碼的荧缘。但是可以從方法的注釋來看他的意思,這個方法作用是拦宣,如果之前這個View的flag被重置過截粗,那么就返回true,反之返回false鸵隧。簡而言之绸罗,相對于同一個View來說的話,如果第一次調用這個方法的話掰派,返回的是false从诲;反之則返回的true。
??所以靡羡,在這里系洛,我們就可以理解到了,只要是在調用dispatchTransformedTouchEvent方法之前就break的話略步,resetCancelNextUpFlag返回的肯定是true描扯。這個是為什么呢?因為只要getTouchTarget返回的不是null趟薄,表示的意思就是當前的View已經被添加到了mFirstTouchTarget所在的鏈表中绽诚,也就是說在當前這個事件之前,有可能有個事件傳遞到當前的這個View杭煎,并且執(zhí)行了恩够,所以被添加到鏈表中的。因為這段代碼在dispatchTransformedTouchEvent方法為的true才執(zhí)行的:
newTouchTarget = addTouchTarget(child, idBitsToAssign);
??從而得知羡铲,只要newTouchTarget不為null的話蜂桶,resetCancelNextUpFlag方法返回的肯定是true。而這里cancelChild變量還由intercepted變量來決定也切,這個待會再細講扑媚,因為變量太特么的坑了!
??這樣我們就能得知雷恃,如果一個View對一個事件序列的事件進行處理疆股,但是后續(xù)如果有一個事件不會處理的話,那這個View會收到一個ACTION_CANCEL類型的事件倒槐!
??以上就是ViewGroup對子View的事件分發(fā)大概的解釋旬痹,不敢說特別詳細!下面來總結一下:
??1.當一個事件傳遞到ViewGroup里面的話,首先會根據事件類型或者mFirstTouchTarget 是否null來判斷是否調用onInterceptTouchEvent方法唱凯,當然這個過程中還要考慮FLAG_DISALLOW_INTERCEPT標記位羡忘。簡而言之,當前DOWN事件來到時磕昼,ViewGroup首先詢問onInterceptTouchEvent是否需要攔截。這里需要注意的是节猿,如果有子View處理這個事件了票从,會導致mFirstTouchTarget不為null,從而可以形成一種父ViewGroup可以攔截非ACTION_DOWN事件的局面滨嘱!還需要注意的是峰鄙,整個詢問攔截的過程還需要考慮子View調用requestDisallowInterceptTouchEvent方法來請求不要我的事件!哎太雨,感覺子View好可憐吟榴,動不動就會ViewGroup折磨!D野狻7苑!
??2.當ViewGroup不對事件進行攔截時锥咸,ViewGroup會將相應的事件傳遞到子View里面狭瞎!
??3.如果整個事件序列的ACTION_DOWN沒有子View來處理,最終會傳遞到ViewGroup方法里面處理搏予。因為當mFirstTouchTarget為null時熊锭,會調用ViewGroup自己的onTouchEvent方法!但是這里需要的注意雪侥,整個事件序列碗殷,除了ACTION_DOWN會傳遞到子View的onTouchEvent之外,后續(xù)的事件都只會到達子View的dispatchTouchEvent方法速缨,不會到達onTouchEvent方法里面锌妻。這個原因待會再講View的方法來解釋!
??4.當一個事件序列中間(記住這里是中間鸟廓,開始的情況參考 3 从祝,結尾可以參考這個)的某個事件沒有子View來處理的話,那么在TouchTarget鏈上的所有View都會收到一個ACTION_CANCEL事件引谜,并且會將這些子View從鏈上recycle掉牍陌。從而得知,只要一個子View不對一個事件進行處理员咽,那么在這個事件序列上的其他類型的事件都不會交給它來處理毒涧。
??5.如果一個事件序列從ACTION_DOWN開始,就被攔截了贝室。這個事件序列的所有事件不會在傳遞的到子View契讲。因為ACTION_DOWN來的時候仿吞,mFirstTouchTarget本來為空,由于onInterceptTouchEvent方法返回true捡偏,所以導致if (!canceled && !intercepted)語句進入不了唤冈,進而導致mFirstTouchTarget在整個事件序列都為空,所以一直在調用這個代碼银伟,從而導致整個事件序列的都不能傳遞下去:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
(3).ViewGroup對View事件的攔截
??還記得我在前面挖的兩個坑嗎你虹?第一個是在概述里面說的,前排預警一下彤避,onInterceptTouchEvent方法有很多的坑傅物,不是返回一個true或者false那么簡單;第二個是在(2)里面的琉预,有沒有辦法對其他事件進行需求性的攔截?
??到這里來看看董饰,這兩個坑好像就像是一個問題,都是關于onInterceptTouchEvent方法圆米。
??其實在之前我們簡單的介紹onInterceptTouchEvent方法的作用和使用卒暂,但是只是粗略的介紹,在這里將稍微詳細的解釋榨咐。
A.onInterceptTouchEvent方法的調用時機
??先說明一下介却,這里先不考慮FLAG_DISALLOW_INTERCEPT標記位的影響。
??在dispatchTouchEvent方法块茁,我們知道齿坷,當一個事件序列的開始,也就是ACTION_DOWN來到時数焊,會調用onInterceptTouchEvent方法來詢問是否需要攔截此事件序列永淌!這種情況下,應該非常容易的理解佩耳!
??另一種情況便是mFirstTouchTarget 不為null的時候遂蛀。那mFirstTouchTarget不為null究竟是什么情況呢?我們從dispatchTouchEvent方法里面可以看出來干厚,當一個事件被子View消耗了李滴,那么會將當前的這個View封裝成一個Target對象,然后添加到一個鏈表的鏈頭蛮瞄,而mFirstTouchTarget則是指向這個鏈表的鏈頭所坯。也就是說,當前mFirstTouchTarget不為null的時候挂捅,表示在同一個事件序列芹助,當前事件前面的事件被子View消耗掉了!mFirstTouchTarget不為null表示的就是這個意思!
??如上的情況下状土,我們可以形象的解釋无蜂,將你的媽媽比喻為ViewGroup,而子View當成你蒙谓,你開始打游戲表示一個事件序列的開始斥季。如上的情況就是這樣的,你開始打游戲的時候彼乌,你媽媽沒有攔截你的行為泻肯,因此你可以順利的打開游戲,開心的吃雞慰照,如果中途你媽媽叫你去打醬油,可是此時你正在決賽圈說你沒空琉朽,你媽媽就生氣了毒租,把你的網線拔了,相當于是攔截你的行為箱叁,導致你的吃雞夢想泡湯了墅垮!這個比喻能夠說明上面的情況,也就是說耕漱,當子View在ACTION_MOVE的非常開心的時候算色,父ViewGroup有資格讓子View不開心!哈哈哈哈C弧T置巍!
??上面的解釋就是妓笙,當不考慮FLAG_DISALLOW_INTERCEPT標記位時若河,onInterceptTouchEvent方法的調用時機。
??那么我們現在來考慮FLAG_DISALLOW_INTERCEPT標記位寞宫。
??首先說一下萧福,標記位對事件序列的開始事件--ACTION_DOWN無效的!只有當子View在ACTION_MOVE的非常開心的時候,才有資格向父ViewGroup申請不要攔截我的事件辈赋!這個請求是有效的鲫忍!
??如上便是onInterceptTouchEvent方法的調用時機。這里對onInterceptTouchEvent方法的調用時機做一個簡單的總結:
??1.ViewGroup有資格一開始ACTION_DOWN,即使子View調用requestDisallowInterceptTouchEvent方法來申請不攔截也沒有用的钥屈。一旦攔截了悟民,整個事件序列就都失去了向下傳遞的能力,直接進入ViewGroup的onTouchEvent方法去處理焕蹄。
??2.View有資格不攔截ACTION_DOWN,而是攔截ACTION_MOVE和ACTION_UP事件逾雄。還是跟攔截ACTION_DOWN的情況比較類似,但是還是有點區(qū)別!
B.onInterceptTouchEvent方法對非ACTION_DOWN的事件進行攔截
??如果ViewGroup只能對ACTION_DOWN進行攔截的話鸦泳,這樣也太暴力了银锻!因為這樣會導致整個事件序列都只能被傳遞ViewGroup。所以做鹰,ViewGroup對ACTION_MOVE和ACTION_UP事件還是有必要的击纬。其實這種需求很好的實現,例如:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP: {
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
??是不是瞬間來了一句臥槽钾麸!這么簡單更振。對!就是這么簡單饭尝,但是簡單的背后大有玄機所在了肯腕!例如:
??這是ViewGroup的代碼:
public class MyViewGroup extends LinearLayout {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("pby123", "1");
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP: {
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("pby123", "2");
return true;
}
}
??這是View的代碼:
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("pby123","3");
return true;
}
}
??此時,我對View進行ACTION_DOWN和ACTION_UP的事件產生钥平,然后打印的log卻是這樣的:
??我們發(fā)現实撒,當ACTION_DOWN事件產生時,傳遞到子View很正常涉瘾,但是我們對ACTION_UP事件進行攔截的知态,為什么還是會傳遞子View里面去呢?是不是onInterceptTouchEvent對ACTION_UP事件是無效的呢立叛?瞎猜是沒有用的负敏,此時我們來看看dispatchTouchEvent的代碼。(其實這種情況秘蛇,我在分析dispatchTouchEvent的時候已經非常小聲的說過了哦F渥觥!M妗J痢)
// Check for interception.
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
??上面這段代碼,我們當ACTION_UP事件來到秽浇,由于mFirstTouchTarget不為null浮庐,最終會調用onInterceptTouchEvent來進行詢問是否需要攔截,我們在onInterceptTouchEvent方法里面返回的是true柬焕,所以在intercepted肯定為true审残。然后代碼往下走,最終進入這段代碼:
// 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) {
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;
}
??是不是感覺又回來了斑举?又來分析這個方法了搅轿,我們知道cancelChild返回的肯定是true,所以dispatchTransformedTouchEvent這一步會給子View發(fā)送一個ACTION_CANCEL事件,然后就行將這個target回收了富玷。
??到這里我們知道了璧坟,第二次ACTION_UP事件根本沒有傳遞到子View里面既穆,傳遞過去的是一個ACTION_CANCEL事件!大家如果不信的話雀鹃,可以去試試幻工!
??這里我們不滿足只是ACTION_DOWN和ACTION_MOVE事件,我們使其也產生ACTION_MOVE事件黎茎。我們來看看這種情況下的log日志:
??哈哈沒錯囊颅,第二次之所以將事件傳遞給子View,那么是因為ACTION_MOVE事件被攔截了傅瞻,從而傳遞過去一個ACTION_CANCEL事件過去踢代,而不是ACTION_MOVE事件。
??好了嗅骄,onInterceptTouchEvent方法分析的差不多了胳挎,現在該解決在留的兩個問題。首先溺森,onInterceptTouchEvent方法坑在于onInterceptTouchEvent方法的調用時機串远,待會再總結里面會總結一下,這里就不再多余的說了儿惫;其實onInterceptTouchEvent的坑還有就是ACTION_DOWN和ACTION_UP,誰又能想到傳遞子View的根本不是ACTION_UP事件呢?伸但。其次肾请,就是對非ACTION_DOWN的攔截,假設我們從ACTION_MOVE開始攔截更胖,需要注意的是第一個ACTION_MOVE事件是不會傳遞子View铛铁,也不會傳遞到ViewGroup,只有經過這次的處理却妨,后面的事件ViewGroup才算是能夠接收到饵逐!
??又該對上面的知識點做一個總結:
??1.一個ViewGroup的調用時機是:1.ACTION_DOWN的來到;2.事件序列中間的ACTION_MOVE事件來到彪标,需要注意是這樣情況下倍权,必須保證在同一個事件序列中, 當前事件的前面的事件有被子View消耗過的捞烟,也就是,mFirstTouchTarget不能為null默辨。
??2.調用時機還需要的是:如果一個事件被攔截了苍息,在這個事件序列里面缩幸,onInterceptTouchEvent不會再被調用。
??3.如果我們想要對非ACTION_DOWN事件進行攔截表谊,必須保證同一個事件序列的前面所有事件都子View執(zhí)行了。
??4.對非ACTION_DOWN事件進行攔截铃肯,是對下次的事件進行攔截患亿,當前的事件會被變?yōu)锳CTION_CANCEL傳遞到子View中去押逼。
3.View對事件的處理
??由于View是沒有子View的挑格,所以View不能繼續(xù)對事件繼續(xù)的分發(fā)。相較于ViewGroup雾消,View少了一個onInterceptTouchEvent方法挫望。所以說媳板,如果一個事件到達View蛉幸,肯定會處理,注意的處理表達意思是:它可以調用onTouch或者onTouchEvent方法來處理提陶,或者不處理隙笆,最后這個事件被它的ViewGroup分發(fā)ViewGroup自己進行處理仲器。
??所以仰冠,View對事件的處理分成兩種情況:一種是自己處理洋只;一種是不處理,父ViewGroup會自己處理肢扯,處理的代碼也是調用View的蔚晨,因為ViewGroup繼承于View铭腕。我們一個一個的分析。
(1).View事件處理流程
??事件首先會被傳遞View的dispatchTouchEvent方法里面浩考,我們來看看析孽,不要怕哦袜瞬!View的dispatchTouchEvent代碼非常的簡單吞滞。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
??以上的代碼,我刪除了部分我認為不重要的代碼赴精,是不是非常的簡單绞幌?其實意思也非常的簡單莲蜘,首先如果設置了OnTouchListener監(jiān)聽的話票渠,onTouch方法是否消耗該事件问顷,如果消耗的話禀梳,事件傳遞就結束了算途;反之嘴瓤,則將事件傳遞到onTouchEvent方法里面去廓脆。
??從這里胆胰,我們可以看出蜀涨,onTouch的優(yōu)先級比onTouchEvent的高!
??我們再來看看onTouchEvent厚柳,由于onTouchEvent方法代碼太長了别垮,這里只看部分:
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
??從以上的代碼中碳想,我們看出來胧奔,如果一個View的enable屬性是Disable的話,它仍然能夠消耗事件胳泉,只是不會做出任何的反應而已扇商,正如注釋所說的案铺。
??我們繼續(xù)往下看红且,我們發(fā)現switch-case語句被這段代碼包裹:
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
......
}
??而clickable是什么呢?我們來看看:
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE
??也就是說嗤放,只要CLICKABLE次酌、LONG_CLICKABLE或者CONTEXT_CLICKABLE其中一個為true的話岳服,就會對事件進行消耗吊宋!
??在switch-case里面璃搜,我們不看ACTION_DOWN和ACTION_MOVE事件鳞上,我們來看看ACTION_UP事件有個非常眼熟的東西:
if (!post(mPerformClick)) {
performClick();
}
??我們再來看看performClick方法里面有什么東西呢
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
??哎呀唾糯,這個不是我們喜聞樂見的OnClickListener嗎移怯?開不開心这难,激不激動?哈哈哈Q慵选L侨ā星澳!
??從這里禁偎,我們可以得出,在一個View中笆檀,onTouch的優(yōu)先級是最高的酗洒,其次是onTouchEvent樱衷,最后才是onClick方法矩桂!
(2).ViewGroup對事件的處理
??ViewGroup對事件的處理在dispatchTransformedTouchEvent方法里面進行的侄榴,由于dispatchTransformedTouchEvent方法的代碼比較長牲蜀,這里只看他是怎么調用onTouchEvent方法:
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;
}
??我們上面的代碼中發(fā)現調用super.dispatchTouchEvent(event)方法涣达,從而完成了自己對事件的處理度苔,事件處理的流程跟View對事件的處理流程比較相似寇窑!
4.總結
??終于寫完了甩骏,我們還是來對我們所有的內容做一個總結: