關於Android事件分發機制的巨集觀理解(簡單)
我儘量不打錯別字,用詞準確,不造成閱讀障礙。
之所以說是巨集觀,是因為我不會寫demo,一步步截圖給你看,我會拿原始碼然後剔除暫時用不到的部分,讓你從大脈絡上理解事件分發機制,這樣你會發現,很簡單。
首先在腦海中應該有一個例子,一個LinearLayout裡面有一個button,沒了,然後你設定了button的setOnClickListener方法,很簡單的例子,也是最能接受的例子。
事件序列: 這是一個集合概念,一個完整的事件序列包含點選、移動、擡起等事件,一般就是down、move、up這三個。
這時你點選了button,就是最普通的點選,這時傳遞了一個down事件和一個up事件,普通點選一般不會觸發move事件。
先說down事件,down通過螢幕傳遞給你的Activity(怎麼傳的不糾結),Activity會傳遞給你的LinearLayout,並觸發了dispatchTouchEvent(MotionEvent ev),dispatch是分發的意思,這中間是怎麼傳的先忽略,總之就是down傳到了你的LinearLayout,要求觸發dispatchTouchEvent(MotionEvent ev)方法(down就儲存在其中的MotionEvent中),看一下LinearLayout,發現沒有這個方法,但是它繼承了ViewGroup,ViewGroup是有這個方法的,所以根據繼承規則會呼叫ViewGroup的dispatchTouchEvent方法去分發事件,disaptchTouchEvent方法很長,我們擷取重要部分:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//...省略部分程式碼
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 {
intercepted = true;
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//***************************省略部分程式碼&&分界線***********************************//
if (!canceled && !intercepted) {//如果攔截了,這方法根本進不來
//...省略部分程式碼
if (newTouchTarget == null && childrenCount != 0) {
//...省略部分程式碼
final View[] children = mChildren;
//迴圈子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
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;
}
}
}
}
return handled;
}
省略了好些程式碼,這個其實分兩部分看,我用線畫出來了,上半部分,down事件會傳遞進來,然後執行onInterceptTouchEvent(ev); 這是攔截方法,Intercept是攔截的意思,預設執行結果是false,就是不攔截,而且這個方法是ViewGroup的內部方法不是繼承而來的,所以只有它有攔截,這個知識點主要是針對ViewGroup的父類——View,它是沒有這個方法的。其中的mFirstTouchTarget在down事件不攔截後就!=null,如果down事件被viewGroup攔截了,就會==null,intercepted就為true,之後的move、up等後續事件進不去那個if判斷,就不會分發給子view,自己消耗。
好,不攔截,然後往下走,橫線下面的if判斷中的canceled正常都是false,取反,intercepted又是false,好,進來了,下面的if正常也是會進去的(看變數名就可以知道一些),其中有一個for迴圈子view,馬上任務就要交給子view了,ViewGroup就歇會兒了。
重點看dispatchTransformedTouchEvent:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//省...
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//省...
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
程式碼要少一些,dispatchTouchEvent(分發)中的dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)傳遞的引數是ev、false、child、idBitsToAssign,也就是cancel永遠為false,child一般不為null,所以正常會呼叫child.dispatchTouchEvent方法,也就是View的dispatchTouhEvent方法,分發,這樣就從父佈局轉移到子view了。
public boolean dispatchTouchEvent(MotionEvent event) {
final int actionMasked = event.getActionMasked();
//省...
if (onFilterTouchEventForSecurity(event)) {
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;
}
走到這裡我們已經經歷兩個dispatchTouchEvent了,希望不要弄混,一個是ViewGroup的,一個是View的,雖然ViewGroup繼承自View,但是對佈局而言,我們要先執行父佈局(ViewGroup)的dispatchTouchEvent,然後在執行自己的dispatchTouchEvent,因為自己沒有這個方法,但自己的父佈局View是有的,所以會執行View的dispatchTouchEvent。
其中的if一般也是可以進來的,然後又是2個if判斷,第一個if判斷的result決定了第二個if判斷會不會執行,在本文中,因為沒有setOnTouchListener,所以第一個if進不來,就會到下一個if,也就會執行onTouchEvent方法:
public boolean onTouchEvent(MotionEvent event) {
//...省
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//...省
if (!post(mPerformClick)) {
performClick();
}
//...省
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
//...省
break;
case MotionEvent.ACTION_CANCEL:
//...省
break;
case MotionEvent.ACTION_MOVE:
//...省
break;
}
return true;
}
return false;
}
當在DOWN事件中,其實從巨集觀角度沒什麼要特別解釋的,包括一些長按判斷等等,就省略了,重點是那個UP事件,當手指擡起的時候UP事件就會被觸發,它也會像down事件一樣經過層層篩選進來最後到view的onTouchEvent這裡,會執行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;
}
這是所有程式碼,沒有省略,其它的也不用管,主要看if判斷中的一句,li.mOnClickListener.onClick(this);是不是看著眼熟?li.mOnClickListener就是我們設定的監聽,當Button設定了setOnClickListener後,我們看看它幹了什麼,首先:button繼承自TextView,TextView繼承自View,button和TextView都沒有這個方法,所以最後會執行view的setOnClickListener,看程式碼:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true); //如果clickable為false就設為true
}
getListenerInfo().mOnClickListener = l;
}
這樣就把我們設定的onClickListener物件賦值給getListenerInfo().mOnClickListener,當執行li.mOnClickListener.onClick(this);時就會執行我們寫的操作。
其實前期可以把View和ViewGroup看成是兩個獨立的類,方便理解,但是很快就應該認識到兩者是繼承關係,並不獨立。
一句話說明就是:事件總會先傳給父元素,父元素進行分發(迴圈)和攔截判斷,不攔截就傳遞給子元素,子元素如果可以點選就會消耗事件,反之則返回給父元素處理。
正常情況下,down事件要經過這個迴圈(但沒有performClick),up事件也要走這個迴圈(有performClick),中間要是有move,就要走多次這個迴圈,挺繁瑣的,所以中間那些省略的程式碼中做了很多處理的,優化了操作步驟,比如:如果down事件被ViewGroup攔截了,整個事件序列要怎麼處理?每個方法都是有boolean型別的返回值的,這些返回值是幹嘛的?這些其實都在原始碼裡,包括我省略的部分,說一千,道一萬都不如自己看原始碼理解的透徹。