??之前分析了一下Android中的消息傳遞機(jī)制,不知道對各位有沒有幫助!哈哈,別怪我寫的太垃圾了......也不要說的太多的廢話了,直接進(jìn)入今天的主題--Android 事件分發(fā)機(jī)制。還是那樣,文章如有錯(cuò)誤,請各位指正,本文參考資料:
??1.任玉剛老師的《Android 開發(fā)藝術(shù)探索》
??2.徐宜生老師的《Android 群英傳》
??注意,本文的所有代碼都是 API 26,如果是其他的版本,會做特別說明!
1.概述
??我們還是繼承一下《Android 消息處理機(jī)制》的格式,先來概述一下今天的內(nèi)容,假裝符合面向?qū)ο蟮睦^承特性。。。
??在事件傳遞機(jī)制中,必須講解的三個(gè)方法:
??1.public boolean dispatchTouchEvent(MotionEvent ev)方法,這個(gè)方法作用主要是用來分發(fā)事件。也就是說,當(dāng)一個(gè)事件傳遞當(dāng)前View的dispatchTouchEvent方法里面,這個(gè)方法可以決定將事件分發(fā)到哪里去,這里的分發(fā)到哪里去表示有兩個(gè)意思:1.將事件分發(fā)到子View(如果有子View的話);2.將事件分發(fā)到分發(fā)到自己的onTouchEvent方法里面去消耗。
??2.public boolean onInterceptTouchEvent(MotionEvent ev)方法,這個(gè)方法的作用是用來決定當(dāng)前的View或者ViewGroup是否攔截這個(gè)事件,如果返回true的話,那么就表示攔截;反之,表示不攔截。前排預(yù)警一下,這個(gè)方法有很多的坑,不是返回一個(gè)true或者false那么簡單。
??3.public boolean onTouchEvent(MotionEvent event)方法,這個(gè)方法是具體消耗事件的方法,如果返回true的話,表示當(dāng)前的View已經(jīng)將這個(gè)事件消耗了。
??可能大家看我寫了這些,還是覺得一臉懵逼。這三個(gè)方法的意思大家都懂,說這些有什么用。大哥,不要急,我們來慢慢的分析。
??當(dāng)前一個(gè)事件發(fā)生了,事件傳遞的流程是從上層依次傳遞到下層,直到這個(gè)事件被處理,例如:
??上圖中,當(dāng)在事件發(fā)生點(diǎn)發(fā)生了事件,它的傳遞順序是:ViewGroupA ->ViewGroupB ->View。然后我們在結(jié)合上面的三個(gè)方法來更加形象的展示一下,事件分發(fā)的順序:
??這里從圖中可以看出來,事件是從ViewGroupA開始的,先調(diào)用A的dispatchTouchEvent方法,進(jìn)行分發(fā),同時(shí)還會調(diào)用A的onInterceptTouchEvent方法,如果onInterceptTouchEvent方法返回的是false,表示ViewGroupA不攔截此事件,于是將事件傳遞給ViewGroupB,ViewGroupB也進(jìn)行跟ViewGroupA一樣的操作。如果ViewGroupB也不進(jìn)行攔截的話,那么首先就會傳遞到View的dispatchTouchEvent方法,由于View再沒有子View了,所以不能進(jìn)行向下分發(fā),所以只能傳遞到View的onTouchEvent方法里面來。如果View消耗了這個(gè)事件的話,那么這個(gè)事件傳遞的流程就在這里結(jié)束,不會繼續(xù)將事件傳到ViewGroupB的onTouchEvent方法里面去;反之如果View不消耗這個(gè)事件的話,那么就繼續(xù)往上傳遞。
??上面只分析了ViewGroup不對事件進(jìn)行攔截的情況,下面來分析一下當(dāng)一個(gè)ViewGroup攔截了事件的情況。例如:
??一旦,ViewGroupA對事件進(jìn)行攔截,直接將事件傳遞給ViewGroupA的onTouchEvent方法里面去。
??這個(gè)大的流程差不多就是這樣的,可能中間有非常多的細(xì)節(jié)沒有提及到,但是不急,待會的源碼分析有你們好受的?。。」_玩笑!
2.ViewGroup的事件分發(fā)
(1).DecorView
??當(dāng)我們用手指在屏幕點(diǎn)擊時(shí),事件首先被傳遞到Activity的dispatchTouchEvent方法。對的哦!你沒有看錯(cuò),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);
}
??可見,當(dāng)Activity的dispatchTouchEvent方法接收到了一個(gè)事件之后,Activity會將這個(gè)傳遞到Window里面去,我們再去看看:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
??哦豁,我們發(fā)現(xiàn)superDispatchTouchEvent所在的Window類是一個(gè)抽象類,怎么辦?不急,在Window類解釋中,google爸爸給我們這么說的(代碼根據(jù) api 26):
The only existing implementation of this abstract class is
android.view.PhoneWindow, which you should instantiate when needing a
Window.
??這里說的是,Window抽象類的唯一實(shí)現(xiàn)類在是android.view.PhoneWindow。然后我們到PhoneWindow里面去看看相應(yīng)的方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
??好嘛,又繼續(xù)跳,然后我們就到了DecorView類的superDispatchTouchEvent方法里面來了。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView又是什么鬼?DecorView其實(shí)我們界面的頂級容器,也就是我們視圖樹的根,是被添加到Window的。而DecorView作為頂級View,一般情況下,它內(nèi)部會類似于LinearLayout的豎直布局,在這個(gè)布局里面有上下兩個(gè)部分,上面是標(biāo)題欄,下面是Content View部分,在Activity 的setContView所設(shè)置的布局文件就是添加到Content View的部分。如圖:
??通常來說,我們可以通過如下代碼來我們自己設(shè)置的ContentView對象
ViewGroup viewGroup = getWindow().getDecorView().findViewById(android.R.id.content);
??從這里,我們知道DecorView肯定是一個(gè)ViewGroup對象,我們繼續(xù)點(diǎn)擊dispatchTouchEvent方法,發(fā)現(xiàn)進(jìn)入到了ViewGroup的dispatchTouchEvent方法里面來了。
??好嘛,費(fèi)了半天的勁,我們終于看到了重頭戲了。好了好了,我們整裝待發(fā),準(zhǔn)備好好的來看一下這個(gè)方法!不過我們先來總結(jié),我們獲取了哪些信息:
??1.一個(gè)事件首先會被傳遞到Activity的dispatchTouchEvent方法里面,然后最終會傳遞DecorView中去,最后通過DecorView調(diào)用ViewGroup的dispatchTouchEvent方法來進(jìn)行事件的分發(fā)。
??2.DecorView是一個(gè)Activity的根本局,實(shí)際上他也是一個(gè)ViewGroup。
(2).ViewGroup對View事件的分發(fā)
??由于dispatchTouchEvent方法源代碼太多了,所以我就不像消息機(jī)制那篇文章貼出完整的代碼,在這里知識貼出部分代碼來進(jìn)行理解。
??首先,我們來看看這段代碼:
// 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當(dāng)前的ViewGroup是否需要攔截當(dāng)前的事件。我們發(fā)現(xiàn)在這段代碼里面發(fā)現(xiàn)了另一個(gè)比較眼熟的方法onInterceptTouchEvent方法。從代碼中,我們可以看出,ViewGroup判斷一個(gè)事件是否需要判斷實(shí)在dispatchTouchEvent方法里面對方法進(jìn)行調(diào)用。
??然后我們再看看調(diào)用onInterceptTouchEvent方法的條件。首先,action為ACTION_DOWN的話,需要判斷當(dāng)前的是否攔截,這個(gè)非常好理解。但是mFirstTouchTarget是什么什么意思?實(shí)際上呢,這個(gè)從后面的代碼邏輯中可以看出來,當(dāng)ViewGroup的子元素成功處理一個(gè)事件的時(shí)候,mFirstTouchTarget會被賦值并指向該子元素。換一句話說,當(dāng)ViewGroup不攔截事件,將事件交由給子元素來處理時(shí),mFirstTouchTarget就不為null了。也就是說,當(dāng)事件序列的開始--ACTION_DOWN來到時(shí),這時(shí)候mFirstTouchTarget是為null(因?yàn)檫@是第一次來,所以事件還沒有傳遞給它的子元素),如果此時(shí)ViewGroup在onInterceptTouchEvent返回為true的話,表示攔截這個(gè)事件序列,然后后面的ACTION_MOVE和ACTION_UP來到時(shí),由于此時(shí)調(diào)用onInterceptTouchEvent方法的條件不符合,所以onInterceptTouchEvent不會再被調(diào)用。為什么這里調(diào)用onInterceptTouchEvent方法的條件不符合呢,因?yàn)榈谝淮蔚膁own事件被ViewGroup攔截了,從而導(dǎo)致down事件沒有被傳遞到子View,所以mFirstTouchTarget肯定為null,當(dāng)ACTION_MOVE和ACTION_UP兩個(gè)事件來到,actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null肯定為false的!
??從而,我們從這段里面得到一個(gè)結(jié)論,一旦一個(gè)ViewGroup在onInterceptTouchEvent方法里面對ACTION_DOWN事件進(jìn)行攔截,屬于同一個(gè)事件序列的后續(xù)事件也會被攔截,同時(shí)onInterceptTouchEvent方法只會被調(diào)用一次,也就是對ACTION_DOWN進(jìn)行攔截的那一次!
??說到這里,那么有沒有辦法對其他事件進(jìn)行需求性的攔截呢?有的,這個(gè)問題,我們后續(xù)再講!現(xiàn)在就講的話,就不能顯示我牛逼了!哈哈,開玩笑的,應(yīng)該時(shí)時(shí)刻刻記住自己就是一個(gè)菜雞!
??在剛剛的那段代碼中,我們還發(fā)現(xiàn)有這個(gè)判斷
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
......
}
??其中,我們需要關(guān)注的是FLAG_DISALLOW_INTERCEPT 標(biāo)記位,這個(gè)標(biāo)記位是通過ViewGroup里面的requestDisallowInterceptTouchEvent方法來設(shè)置的,一般用于子View。一旦FLAG_DISALLOW_INTERCEPT被設(shè)置了,也就是說,我們在子View里面調(diào)用父布局的requestDisallowInterceptTouchEvent方法,那么ViewGroup將無法攔截除ACTION_DOWN以外的其他點(diǎn)擊事件。
??這里為什么時(shí)候是ACTION_DOWN以外的點(diǎn)擊事件呢?這是因?yàn)?,ACTION_DOWN事件會重置FLAG_DISALLOW_INTERCEPT標(biāo)記位,導(dǎo)致子View設(shè)置的這個(gè)標(biāo)記位無效。我們來看看代碼:
// 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,從而導(dǎo)致我們的子View調(diào)用requestDisallowInterceptTouchEvent方法失效!
??經(jīng)過上面的代碼,如果ViewGroup不對事件進(jìn)行攔截,那么就會將這個(gè)事件分發(fā)到能夠接收到這個(gè)事件的子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();
}
??根據(jù)任玉剛老師在《Android 開發(fā)藝術(shù)探索》中對這段代碼的解釋,一個(gè)子View是否能夠接收到點(diǎn)擊事件主要由兩點(diǎn)來衡量:子View是否是否在播放動畫和點(diǎn)擊事件是否落在子View的的區(qū)域內(nèi)。如果這兩個(gè)事件能夠滿足的話,那么事件就會交給它來處理。
??這里將會詳細(xì)的講解一下,事件到底是怎么傳遞到子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;
}
??這個(gè)是事件分發(fā)代碼,其中,我們會發(fā)現(xiàn),如果當(dāng)前子View會消耗這個(gè)事件,也就是說dispatchTransformedTouchEvent方法返回true,那么將會將當(dāng)前的View添加target的鏈表,而我們說的mFirstTouchTarget就是指向這個(gè)鏈表的頭!這個(gè)就相當(dāng)于完成的分發(fā)了嗎?
??NO!NO!沒有那么的簡單,我們會發(fā)現(xiàn)前面有段代碼:
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;
}
??如果當(dāng)遍歷第一個(gè)子View的時(shí)候,這里的newTouchTarget就會返回的不是null,豈不是下面的dispatchTransformedTouchEvent根本就來不及調(diào)用。像這種情況,應(yīng)該怎么辦?我們發(fā)現(xiàn),只要在這段代碼里面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;
}
??如果說,之前已經(jīng)將事件分發(fā)下去了,alreadyDispatchedToNewTouchTarget && target == newTouchTarget這個(gè)條件肯定為true。所以,如果在dispatchTransformedTouchEvent方法之前break,從而導(dǎo)致跳出循環(huán),alreadyDispatchedToNewTouchTarget肯定是為false的,因?yàn)檫@個(gè)變量在調(diào)用了dispatchTransformedTouchEvent方法之后會被置為true。這行代碼在之前循環(huán)遍歷子View里面。
alreadyDispatchedToNewTouchTarget = true;
??所以,只要在之前沒有調(diào)用dispatchTransformedTouchEvent方法就break,肯定會進(jìn)入else的代碼里面?,F(xiàn)在的關(guān)鍵是理解resetCancelNextUpFlag是什么意思?我們先來看看這個(gè)方法:
/**
* 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;
}
??這里,我是看不懂代碼的。但是可以從方法的注釋來看他的意思,這個(gè)方法作用是,如果之前這個(gè)View的flag被重置過,那么就返回true,反之返回false。簡而言之,相對于同一個(gè)View來說的話,如果第一次調(diào)用這個(gè)方法的話,返回的是false;反之則返回的true。
??所以,在這里,我們就可以理解到了,只要是在調(diào)用dispatchTransformedTouchEvent方法之前就break的話,resetCancelNextUpFlag返回的肯定是true。這個(gè)是為什么呢?因?yàn)橹灰猤etTouchTarget返回的不是null,表示的意思就是當(dāng)前的View已經(jīng)被添加到了mFirstTouchTarget所在的鏈表中,也就是說在當(dāng)前這個(gè)事件之前,有可能有個(gè)事件傳遞到當(dāng)前的這個(gè)View,并且執(zhí)行了,所以被添加到鏈表中的。因?yàn)檫@段代碼在dispatchTransformedTouchEvent方法為的true才執(zhí)行的:
newTouchTarget = addTouchTarget(child, idBitsToAssign);
??從而得知,只要newTouchTarget不為null的話,resetCancelNextUpFlag方法返回的肯定是true。而這里cancelChild變量還由intercepted變量來決定,這個(gè)待會再細(xì)講,因?yàn)樽兞刻孛吹目恿耍?br>
??這樣我們就能得知,如果一個(gè)View對一個(gè)事件序列的事件進(jìn)行處理,但是后續(xù)如果有一個(gè)事件不會處理的話,那這個(gè)View會收到一個(gè)ACTION_CANCEL類型的事件!
??以上就是ViewGroup對子View的事件分發(fā)大概的解釋,不敢說特別詳細(xì)!下面來總結(jié)一下:
??1.當(dāng)一個(gè)事件傳遞到ViewGroup里面的話,首先會根據(jù)事件類型或者mFirstTouchTarget 是否null來判斷是否調(diào)用onInterceptTouchEvent方法,當(dāng)然這個(gè)過程中還要考慮FLAG_DISALLOW_INTERCEPT標(biāo)記位。簡而言之,當(dāng)前DOWN事件來到時(shí),ViewGroup首先詢問onInterceptTouchEvent是否需要攔截。這里需要注意的是,如果有子View處理這個(gè)事件了,會導(dǎo)致mFirstTouchTarget不為null,從而可以形成一種父ViewGroup可以攔截非ACTION_DOWN事件的局面!還需要注意的是,整個(gè)詢問攔截的過程還需要考慮子View調(diào)用requestDisallowInterceptTouchEvent方法來請求不要我的事件!哎,感覺子View好可憐,動不動就會ViewGroup折磨?。。?!
??2.當(dāng)ViewGroup不對事件進(jìn)行攔截時(shí),ViewGroup會將相應(yīng)的事件傳遞到子View里面!
??3.如果整個(gè)事件序列的ACTION_DOWN沒有子View來處理,最終會傳遞到ViewGroup方法里面處理。因?yàn)楫?dāng)mFirstTouchTarget為null時(shí),會調(diào)用ViewGroup自己的onTouchEvent方法!但是這里需要的注意,整個(gè)事件序列,除了ACTION_DOWN會傳遞到子View的onTouchEvent之外,后續(xù)的事件都只會到達(dá)子View的dispatchTouchEvent方法,不會到達(dá)onTouchEvent方法里面。這個(gè)原因待會再講View的方法來解釋!
??4.當(dāng)一個(gè)事件序列中間(記住這里是中間,開始的情況參考 3 ,結(jié)尾可以參考這個(gè))的某個(gè)事件沒有子View來處理的話,那么在TouchTarget鏈上的所有View都會收到一個(gè)ACTION_CANCEL事件,并且會將這些子View從鏈上recycle掉。從而得知,只要一個(gè)子View不對一個(gè)事件進(jìn)行處理,那么在這個(gè)事件序列上的其他類型的事件都不會交給它來處理。
??5.如果一個(gè)事件序列從ACTION_DOWN開始,就被攔截了。這個(gè)事件序列的所有事件不會在傳遞的到子View。因?yàn)锳CTION_DOWN來的時(shí)候,mFirstTouchTarget本來為空,由于onInterceptTouchEvent方法返回true,所以導(dǎo)致if (!canceled && !intercepted)語句進(jìn)入不了,進(jìn)而導(dǎo)致mFirstTouchTarget在整個(gè)事件序列都為空,所以一直在調(diào)用這個(gè)代碼,從而導(dǎo)致整個(gè)事件序列的都不能傳遞下去:
// 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事件的攔截
??還記得我在前面挖的兩個(gè)坑嗎?第一個(gè)是在概述里面說的,前排預(yù)警一下,onInterceptTouchEvent方法有很多的坑,不是返回一個(gè)true或者false那么簡單;第二個(gè)是在(2)里面的,有沒有辦法對其他事件進(jìn)行需求性的攔截?
??到這里來看看,這兩個(gè)坑好像就像是一個(gè)問題,都是關(guān)于onInterceptTouchEvent方法。
??其實(shí)在之前我們簡單的介紹onInterceptTouchEvent方法的作用和使用,但是只是粗略的介紹,在這里將稍微詳細(xì)的解釋。
A.onInterceptTouchEvent方法的調(diào)用時(shí)機(jī)
??先說明一下,這里先不考慮FLAG_DISALLOW_INTERCEPT標(biāo)記位的影響。
??在dispatchTouchEvent方法,我們知道,當(dāng)一個(gè)事件序列的開始,也就是ACTION_DOWN來到時(shí),會調(diào)用onInterceptTouchEvent方法來詢問是否需要攔截此事件序列!這種情況下,應(yīng)該非常容易的理解!
??另一種情況便是mFirstTouchTarget 不為null的時(shí)候。那mFirstTouchTarget不為null究竟是什么情況呢?我們從dispatchTouchEvent方法里面可以看出來,當(dāng)一個(gè)事件被子View消耗了,那么會將當(dāng)前的這個(gè)View封裝成一個(gè)Target對象,然后添加到一個(gè)鏈表的鏈頭,而mFirstTouchTarget則是指向這個(gè)鏈表的鏈頭。也就是說,當(dāng)前mFirstTouchTarget不為null的時(shí)候,表示在同一個(gè)事件序列,當(dāng)前事件前面的事件被子View消耗掉了!mFirstTouchTarget不為null表示的就是這個(gè)意思!
??如上的情況下,我們可以形象的解釋,將你的媽媽比喻為ViewGroup,而子View當(dāng)成你,你開始打游戲表示一個(gè)事件序列的開始。如上的情況就是這樣的,你開始打游戲的時(shí)候,你媽媽沒有攔截你的行為,因此你可以順利的打開游戲,開心的吃雞,如果中途你媽媽叫你去打醬油,可是此時(shí)你正在決賽圈說你沒空,你媽媽就生氣了,把你的網(wǎng)線拔了,相當(dāng)于是攔截你的行為,導(dǎo)致你的吃雞夢想泡湯了!這個(gè)比喻能夠說明上面的情況,也就是說,當(dāng)子View在ACTION_MOVE的非常開心的時(shí)候,父ViewGroup有資格讓子View不開心!哈哈哈哈!!!
??上面的解釋就是,當(dāng)不考慮FLAG_DISALLOW_INTERCEPT標(biāo)記位時(shí),onInterceptTouchEvent方法的調(diào)用時(shí)機(jī)。
??那么我們現(xiàn)在來考慮FLAG_DISALLOW_INTERCEPT標(biāo)記位。
??首先說一下,標(biāo)記位對事件序列的開始事件--ACTION_DOWN無效的!只有當(dāng)子View在ACTION_MOVE的非常開心的時(shí)候,才有資格向父ViewGroup申請不要攔截我的事件!這個(gè)請求是有效的!
??如上便是onInterceptTouchEvent方法的調(diào)用時(shí)機(jī)。這里對onInterceptTouchEvent方法的調(diào)用時(shí)機(jī)做一個(gè)簡單的總結(jié):
??1.ViewGroup有資格一開始ACTION_DOWN,即使子View調(diào)用requestDisallowInterceptTouchEvent方法來申請不攔截也沒有用的。一旦攔截了,整個(gè)事件序列就都失去了向下傳遞的能力,直接進(jìn)入ViewGroup的onTouchEvent方法去處理。
??2.View有資格不攔截ACTION_DOWN,而是攔截ACTION_MOVE和ACTION_UP事件。還是跟攔截ACTION_DOWN的情況比較類似,但是還是有點(diǎn)區(qū)別!
B.onInterceptTouchEvent方法對非ACTION_DOWN的事件進(jìn)行攔截
??如果ViewGroup只能對ACTION_DOWN進(jìn)行攔截的話,這樣也太暴力了!因?yàn)檫@樣會導(dǎo)致整個(gè)事件序列都只能被傳遞ViewGroup。所以,ViewGroup對ACTION_MOVE和ACTION_UP事件還是有必要的。其實(shí)這種需求很好的實(shí)現(xiàn),例如:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP: {
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
??是不是瞬間來了一句臥槽!這么簡單。對!就是這么簡單,但是簡單的背后大有玄機(jī)所在了!例如:
??這是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;
}
}
??此時(shí),我對View進(jìn)行ACTION_DOWN和ACTION_UP的事件產(chǎn)生,然后打印的log卻是這樣的:
??我們發(fā)現(xiàn),當(dāng)ACTION_DOWN事件產(chǎn)生時(shí),傳遞到子View很正常,但是我們對ACTION_UP事件進(jìn)行攔截的,為什么還是會傳遞子View里面去呢?是不是onInterceptTouchEvent對ACTION_UP事件是無效的呢?瞎猜是沒有用的,此時(shí)我們來看看dispatchTouchEvent的代碼。(其實(shí)這種情況,我在分析dispatchTouchEvent的時(shí)候已經(jīng)非常小聲的說過了哦!?。。。?/p>
// 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;
}
??上面這段代碼,我們當(dāng)ACTION_UP事件來到,由于mFirstTouchTarget不為null,最終會調(diào)用onInterceptTouchEvent來進(jìn)行詢問是否需要攔截,我們在onInterceptTouchEvent方法里面返回的是true,所以在intercepted肯定為true。然后代碼往下走,最終進(jìn)入這段代碼:
// 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;
}
??是不是感覺又回來了?又來分析這個(gè)方法了,我們知道cancelChild返回的肯定是true,所以dispatchTransformedTouchEvent這一步會給子View發(fā)送一個(gè)ACTION_CANCEL事件,然后就行將這個(gè)target回收了。
??到這里我們知道了,第二次ACTION_UP事件根本沒有傳遞到子View里面,傳遞過去的是一個(gè)ACTION_CANCEL事件!大家如果不信的話,可以去試試!
??這里我們不滿足只是ACTION_DOWN和ACTION_MOVE事件,我們使其也產(chǎn)生ACTION_MOVE事件。我們來看看這種情況下的log日志:
??哈哈沒錯(cuò),第二次之所以將事件傳遞給子View,那么是因?yàn)锳CTION_MOVE事件被攔截了,從而傳遞過去一個(gè)ACTION_CANCEL事件過去,而不是ACTION_MOVE事件。
??好了,onInterceptTouchEvent方法分析的差不多了,現(xiàn)在該解決在留的兩個(gè)問題。首先,onInterceptTouchEvent方法坑在于onInterceptTouchEvent方法的調(diào)用時(shí)機(jī),待會再總結(jié)里面會總結(jié)一下,這里就不再多余的說了;其實(shí)onInterceptTouchEvent的坑還有就是ACTION_DOWN和ACTION_UP,誰又能想到傳遞子View的根本不是ACTION_UP事件呢?。其次,就是對非ACTION_DOWN的攔截,假設(shè)我們從ACTION_MOVE開始攔截,需要注意的是第一個(gè)ACTION_MOVE事件是不會傳遞子View,也不會傳遞到ViewGroup,只有經(jīng)過這次的處理,后面的事件ViewGroup才算是能夠接收到!
??又該對上面的知識點(diǎn)做一個(gè)總結(jié):
??1.一個(gè)ViewGroup的調(diào)用時(shí)機(jī)是:1.ACTION_DOWN的來到;2.事件序列中間的ACTION_MOVE事件來到,需要注意是這樣情況下,必須保證在同一個(gè)事件序列中, 當(dāng)前事件的前面的事件有被子View消耗過的,也就是,mFirstTouchTarget不能為null。
??2.調(diào)用時(shí)機(jī)還需要的是:如果一個(gè)事件被攔截了,在這個(gè)事件序列里面,onInterceptTouchEvent不會再被調(diào)用。
??3.如果我們想要對非ACTION_DOWN事件進(jìn)行攔截,必須保證同一個(gè)事件序列的前面所有事件都子View執(zhí)行了。
??4.對非ACTION_DOWN事件進(jìn)行攔截,是對下次的事件進(jìn)行攔截,當(dāng)前的事件會被變?yōu)锳CTION_CANCEL傳遞到子View中去。
3.View對事件的處理
??由于View是沒有子View的,所以View不能繼續(xù)對事件繼續(xù)的分發(fā)。相較于ViewGroup,View少了一個(gè)onInterceptTouchEvent方法。所以說,如果一個(gè)事件到達(dá)View,肯定會處理,注意的處理表達(dá)意思是:它可以調(diào)用onTouch或者onTouchEvent方法來處理,或者不處理,最后這個(gè)事件被它的ViewGroup分發(fā)ViewGroup自己進(jìn)行處理。
??所以,View對事件的處理分成兩種情況:一種是自己處理;一種是不處理,父ViewGroup會自己處理,處理的代碼也是調(diào)用View的,因?yàn)閂iewGroup繼承于View。我們一個(gè)一個(gè)的分析。
(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;
}
??以上的代碼,我刪除了部分我認(rèn)為不重要的代碼,是不是非常的簡單?其實(shí)意思也非常的簡單,首先如果設(shè)置了OnTouchListener監(jiān)聽的話,onTouch方法是否消耗該事件,如果消耗的話,事件傳遞就結(jié)束了;反之,則將事件傳遞到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;
}
??從以上的代碼中,我們看出來,如果一個(gè)View的enable屬性是Disable的話,它仍然能夠消耗事件,只是不會做出任何的反應(yīng)而已,正如注釋所說的。
??我們繼續(xù)往下看,我們發(fā)現(xiàn)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其中一個(gè)為true的話,就會對事件進(jìn)行消耗!
??在switch-case里面,我們不看ACTION_DOWN和ACTION_MOVE事件,我們來看看ACTION_UP事件有個(gè)非常眼熟的東西:
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;
}
??哎呀,這個(gè)不是我們喜聞樂見的OnClickListener嗎?開不開心,激不激動?哈哈哈?。。?br> ??從這里,我們可以得出,在一個(gè)View中,onTouch的優(yōu)先級是最高的,其次是onTouchEvent,最后才是onClick方法!
(2).ViewGroup對事件的處理
??ViewGroup對事件的處理在dispatchTransformedTouchEvent方法里面進(jìn)行的,由于dispatchTransformedTouchEvent方法的代碼比較長,這里只看他是怎么調(diào)用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ā)現(xiàn)調(diào)用super.dispatchTouchEvent(event)方法,從而完成了自己對事件的處理,事件處理的流程跟View對事件的處理流程比較相似!
4.總結(jié)
??終于寫完了,我們還是來對我們所有的內(nèi)容做一個(gè)總結(jié):