瞭解過View事件分發源碼的同窗或多或少都知道一些事件分發的相關結論,好比某個View若是攔截了事件,那麼它的onTouchEventIntercept方法就不會再次調用
,好比事件若是被某個View消耗掉,那麼該序列中的剩餘事件都將交給該View處理
等等。android
View事件分發的三個核心方法有三個,分別是dispatchTouchEvent
方法,onInterceptTouchEvent
方法和onInterceptTouchEvent
方法。bash
dispatchTouchEvent方法主要用來進行事件的分發。若是事件可以傳遞給當前View,那麼此方法必定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。ide
onInterceptTouchEvent方法在dispatchTouchEvent方法內部調用,用來判斷是否攔截某個事件,返回結果表示是否攔截當前事件。佈局
onTouchEvent方法也在dispatchTouchEvent方法中調用,返回結果表示是否消耗當前事件。ui
今天打算針對其中兩個最有用、最難記和最難理解的結論,從源碼層面給出它們的解釋,結論以下:this
結論1、某個ViewGroup一旦決定攔截(ACTINON_DOWN: onInterceptTouchEvent的返回值爲true),那麼這一個事件序列都只能由它來處理(若是事件序列可以傳遞給它的話),而且它的onInterceptTouchEvent不會再被調用。spa
就是說當一個ViewGroup決定攔截一個事件後,那麼系統會把同一個事件序列內的其餘方法都直接交給它來處理,所以就再也不調用這個View的onInterceptTouchEvent去詢問它是否要攔截了。rest
結論2、某個View一旦開始處理事件,若是它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其餘事件都不會再交給它來處理,而且事件將從新交由它的父元素去處理,即父元素的onTouchEvent會被調用。code
View事件分發其餘的結論,本文這裏暫不作分析,讀者可自行了解。orm
咱們自定義三個View,分別繼承自RelativeLayout、LinearLayout、TextView,分別重寫他們的dispatchTouchEvent
方法,onInterceptTouchEvent
方法(這個方法是ViewGroup特有的,View沒有)和onInterceptTouchEvent
方法。
在這三個方法內部,不作任何邏輯修改,只依次打印ACTION_DOWN、ACTION_MOVE、ACTION_UP的log。具體代碼以下所示:
public class MyLinearLayout extends LinearLayout {
private static final String TAG = MyLinearLayout.class.getSimpleName();
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG + " dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG + " dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG + " dispatchTouchEvent ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG + " onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG + " onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG + " onInterceptTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG + " onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG + " onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG + " onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
複製代碼
MyRelativeLayout的代碼和MyLinearLayout徹底一致,MyTextView稍有不一樣,由於它沒有onInterceptTouchEvent
方法。
接着在佈局中使用這三個自定義View,最外側是MyRelativeLayout,子View是一個MyLinearLayout,再內部是MyTextView。具體代碼以下所示:
<com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyRelativeLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#BCEAC1"
android:contentDescription="MyLinearLayout">
<com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyLinearLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:background="#2DC98F"
android:contentDescription="MyLinearLayout"
android:orientation="vertical">
<com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyTextView
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:layout_marginTop="50dp"
android:background="#839885"
android:gravity="center"
android:text="MyTextView" />
</com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyLinearLayout>
</com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyRelativeLayout>
複製代碼
效果以下圖:
而後咱們點擊MyTextView區域,輸出log以下:
MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyTextView dispatchTouchEvent ACTION_DOWN
MyTextView onTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
MyRelativeLayout onTouchEvent ACTION_DOWN
複製代碼
能夠看出,在不作任何處理的狀況下,事件是從父View依次向子View傳遞的。會先調用父View的dispatchTouchEvent方法和onInterceptTouchEvent方法,接着調用子View的dispatchTouchEvent方法和onInterceptTouchEvent方法,依次遞歸,直到到達最底層View。
達到最底層View後,會調用最底層View的dispatchTouchEvent方法和onTouchEvent方法,接着事件又依次向上傳遞,直到被消費掉,若是沒有被消費,則會傳遞給最外層View。
在咱們這個例子中,具體傳遞就以下圖所示:
須要說明的是,咱們這個例子中只打印了ACTION_DOWN
的相關log,沒有打印ACTION_MOVE
和ACTION_UP
的,不會由於我偷懶省略,而是由於在事件傳遞過程當中沒有View攔截ACTION_DOWN
事件,也沒有View消費ACTION_DOWN
事件,則同一事件序列中的其餘事件都不會傳遞給這些View來進行處理了,都會交由父View的onTouchEvent方法進行處理。
這個例子很好的說明了結論二
,至於具體源碼咱們後續再看。
咱們回顧下結論一,某個ViewGroup一旦決定攔截(ACTINON_DOWN: onInterceptTouchEvent的返回值爲true),那麼這一個事件序列都只能由它來處理(若是事件序列可以傳遞給它的話),而且它的onInterceptTouchEvent不會再被調用
。
因此咱們在示例一的基礎上,修改MyLinearLayout類,讓其攔截並消耗ACTINON_DOWN事件。
具體來講就是在MyLinearrLayout#onInterceptTouchEvent中,當MotionEvent爲ACTION_DOWN時,就返回true;在MyLinearLayout#onTouchEvent方法返回true。具體代碼以下所示:
public class MyLinearLayout extends LinearLayout {
...
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG + " onInterceptTouchEvent ACTION_DOWN");
return true;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG + " onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG + " onInterceptTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG + " onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG + " onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG + " onTouchEvent ACTION_UP");
break;
default:
break;
}
return true;
}
}
複製代碼
佈局不變,跟上個例子一致,而後咱們點擊MyTextView區域,輸出log以下:
MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
MyRelativeLayout dispatchTouchEvent ACTION_MOVE
MyRelativeLayout onInterceptTouchEvent ACTION_MOVE
MyLinearLayout dispatchTouchEvent ACTION_MOVE
MyLinearLayout onTouchEvent ACTION_MOVE
MyRelativeLayout dispatchTouchEvent ACTION_UP
MyRelativeLayout onInterceptTouchEvent ACTION_UP
MyLinearLayout dispatchTouchEvent ACTION_UP
MyLinearLayout onTouchEvent ACTION_UP
複製代碼
能夠看出事件沒有傳遞給子View MyTextView,而是被MyLinearLayout#onInterceptTouchEvent方法攔截,接着被MyLinearLayout#onTouchEvent方法消費了。
接着同一時間序列中剩下的ACTION_MOVE
、ACTION_UP
事件傳遞給了MyLinearLayout,也由其消費掉了。
另外,MyLinearLayout#onInterceptTouchEvent只被調用了一次,後續的ACTION_MOVE
、ACTION_UP
事件到來時均沒有再次調用onInterceptTouchEvent方法。
這個例子完美的展現了咱們結論一
的現象,具體過程以下圖所示:
例子演示完畢,接下來咱們從源碼角度對這兩個結論進行解析。
咱們知道,ViewGroup的onInterceptTouchEvent方法默認返回false,也就是不攔截。ViewGroup中沒有重寫onTouchEvent
方法,它使用的是View中的onTouchEvent
方法。
經過查看經常使用的LinearLayout、RealtiveLayout、FrameLayout的源碼能夠發現,它們均沒有重寫dispatchTouchEvent
、onInterceptTouchEvent
、onTouchEvent
這三個方法,也就是說他們中的事件分發都是遵循ViewGroup中的規則。
源碼基於android-28。
爲了簡單起見,咱們分段解讀ViewGroup#dispatchTouchEvent方法源碼代碼。
跟事件序列保持一致,咱們依次分析MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP事件。
爲了方便閱讀,咱們將核心代碼分爲了四部分:
part1:
// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
複製代碼
上述這兩個方法內部都會調用clearTouchTargets方法,該方法會將mFirstTouchTarget置爲null。
何爲mFirstTouchTarget? 當子View成功處理事件時,mFirstTouchTarget會被賦值並指向子元素。
接下來看下是否攔截的邏輯,這裏調用了onInterceptTouchEvent方法。
part2:
// 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 {
intercepted = true;
}
複製代碼
先看外層的if判斷,當是ACTION_DOWN事件或者mFirstTouchTarget不爲空時,就會走if內部邏輯,不然就給intercepted賦值爲true,表示攔截事件。咱們此次是ACTION_DOWN事件,因此就會走if內部邏輯。
咱們再看if爲true時的內部邏輯: 先判斷是否設置了FLAG_DISALLOW_INTERCEPT
標記位,默認不設置。該標記位經過requestDisallowInterceptTouchEvent
方法設置,這跟咱們此次分析關係不大,暫且不關注。
disallowIntercept的值默認爲false,因此就走調用子if內部的邏輯,會調用咱們的onInterceptTouchEvent
方法,該方法默認返回值爲false,表示不攔截。
而咱們重寫了MyLinearLayout的onInterceptTouchEvent方法,因此咱們最終的intercepted的值爲true。
接着往下看part3:
// 咱們intercepted的值爲true,因此不會走下面的if內部邏輯。
if (!canceled && !intercepted) {
...
// 若是是ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
// 開啓for循環,依次遍歷全部子View
for (int i = childrenCount - 1; i >= 0; i--) {
...
// 調用dispatchTransformedTouchEvent方法進行事件分發。
// dispatchTransformedTouchEvent方法若是返回true,表示事件被消費掉了。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
// 在addTouchTarget方法內部,會給mFirstTouchTarget賦值。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
...
}
...
}
}
複製代碼
此時intercepted的值爲true,因此不會走下面的if內部邏輯。也就不會給mFirstTouchTarget賦值,mFirstTouchTarget的值依然爲null。
接着看,part4:
// Dispatch to touch targets.
// mFirstTouchTarget的值爲空,因此會走if內部的邏輯,會調用dispatchTransformedTouchEvent方法,不過此次的child參數爲null。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
複製代碼
這裏mFirstTouchTarget爲null,因此會調用dispatchTransformedTouchEvent方法,且child參數值爲null。
咱們這裏看下ViewGroup#dispatchTransformedTouchEvent方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
// Perform any necessary transformations and dispatch.
// 判斷child參數是否爲空
if (child == null) {
// 調用View#dispatchTouchEvent方法。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
// 將事件傳遞給子View。
handled = child.dispatchTouchEvent(transformedEvent);
}
...
return handled;
}
複製代碼
上述代碼能夠看出,在dispatchTransformedTouchEvent方法內部,若是child不爲空,就會調用child.dispatchTouchEvent方法,將事件傳遞給child。
若是child參數爲空,就調用super.dispatchTouchEvent(transformedEvent)方法,也就是View#dispatchTouchEvent方法進行事件分發,在其內部就會調用當前ViewGroup的onTouchEvent方法進行事件處理。
咱們這裏Child爲null,因此會調用View#dispatchTouchEvent方法,在其內部會調用onTouchEvent方法。由於咱們重寫了MyLinearLayout#onTouchEvent方法,讓其返回值爲true,表示事件被消費掉,因此事件就不會再向上傳遞了。
上面分析過,dispatchTransformedTouchEvent方法中的child參數若是爲null的話,就會調用當前View#dispatchTouchEvent方法。
咱們看下View的dispatchTouchEvent方法,源碼以下:
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
...
//noinspection SimplifiableIfStatement
// 若是設置了OnTouchListener,就調用mOnTouchListener#onTouch方法。
// 若是mOnTouchListener#onTouch方法返回值爲true,則result的值爲true。
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 若是result爲false,則調用onTouchEvent方法。
// 若是onTouchEvent方法返回值爲true,則result賦值爲true,表示事件被處理。
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
複製代碼
咱們沒有給MyLinearLayout設置OnTouchListener,因此MyLinearLayout的onTouchEvent方法會執行。
經過上面的分析,咱們能夠解釋ACTION_DOWN的相關log了:
MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
複製代碼
事件傳遞給MyRelativeLayout,先調用MyRelativeLayout#dispatchTouchEvent方法,接着將MyRelativeLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted,而後調用dispatchTransformedTouchEvent方法(child不爲空),將事件分發給子View MyLinearLayout。
在MyLinearLayout內部也是先調用MyLinearLayout#dispatchTouchEvent方法,接着將MyLinearLayout#onInterceptTouchEvent方法的返回值(true)賦值給intercepted,因爲這裏進行了攔截,因此事件不會分發給子View(part3部分
),因此mFirstTouchTarget也爲null,因此會調用dispatchTransformedTouchEvent方法(child參數值爲null, part4部分
),接着調用MyLinearLayout#onTouchEvent方法,該方法也被重寫了,返回值爲true。
因爲MyLinearLayout#onTouchEvent方法返回true,因此MyLinearLayout#dispatchTouchEvent方法也就返回true,因此在MyRelativeLayout#dispatchTouchEvent方法中,將事件分發給子View時(part3部分
),dispatchTransformedTouchEvent方法的返回值爲true,因此mFirstTouchEvent的值不爲null,因此事件不會再次向上傳遞給MyRelativeLayout了。
作下總結,在ACTION_DOWN事件被MyLinearLayout處理完以後,mFirstTouchTarget
的值爲null。
ACTION_MOVE事件到來時,會依舊走一遍dispatchTouchEvent方法。咱們依舊按照part一、part二、part三、part4的順序來看。
part1:
// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
複製代碼
此次傳遞的事ACTION_MOVE事件,因此這個邏輯不會走.
part2:
// 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 {
intercepted = true;
}
複製代碼
這裏咱們是ACTION_MOVE事件,且mFirstTouchTarget
爲null,因此直接走的是外層if的else邏輯,即intercepted = true
,因此這裏不會調用onInterceptTouchEvent方法。
接着往下看,part3:
// 此時intercepted的值爲true,因此不會走下面的if內部邏輯。
if (!canceled && !intercepted) {
...
// 若是是ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
// 開啓for循環,依次遍歷全部子View
for (int i = childrenCount - 1; i >= 0; i--) {
...
// 調用dispatchTransformedTouchEvent方法進行事件分發。
// dispatchTransformedTouchEvent方法若是返回true,表示事件被消費掉了。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
// 在addTouchTarget方法內部,會給mFirstTouchTarget賦值。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
...
}
...
}
}
複製代碼
此時intercepted的值爲true,因此不會走下面的if內部邏輯。
接着看,part4:
// Dispatch to touch targets.
// mFirstTouchTarget的值爲空,因此會走if內部的邏輯,會調用dispatchTransformedTouchEvent方法,不過此次的child參數爲null。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
複製代碼
這裏會調用onTouchEvent方法,將其返回值賦給handled。
咱們分析下ACTION_MOVE相關log:
MyRelativeLayout dispatchTouchEvent ACTION_MOVE
MyRelativeLayout onInterceptTouchEvent ACTION_MOVE
MyLinearLayout dispatchTouchEvent ACTION_MOVE
MyLinearLayout onTouchEvent ACTION_MOVE
複製代碼
事件傳遞給MyRelativeLayout,先調用MyRelativeLayout#dispatchTouchEvent方法,接着將MyRelativeLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted,而後調用dispatchTransformedTouchEvent方法(child不爲空),將事件分發給子View MyLinearLayout。
在MyLinearLayout內部也是先調用MyLinearLayout#dispatchTouchEvent方法,接着因爲不知足part2
中if判斷的條件,因此intercepted的值爲true,也就沒有調用MyLinearLayout#onInterceptTouchEvent方法。
因爲intercepted值爲true,因此不會走part3
的邏輯,因此mFirstTouchTarget的值爲null。
接着因爲mFirstTouchTarget值爲null,因此在part4
中會調用MyLinearLayout#dispatchTransformedTouchEvent方法(child爲null),將事件傳遞給MyLinearLayout#onTouchEvent方法。
因爲咱們重寫了MyLinearLayout#onTouchEvent方法,讓其返回值爲true,因此MyLinearLayout#dispatchTouchEvent方法也就返回true,因此在MyRelativeLayout#dispatchTouchEvent方法中,將事件分發給子View時,dispatchTransformedTouchEvent方法的返回值爲true,因此MyRelativeLayout#mFirstTouchEvent的值不爲null,因此事件不會再次向上傳遞給MyRelativeLayout了。
ACTION_UP流程與ACTION_MOVE流程徹底一致,這裏再也不贅述。
經過以上分析,咱們從源碼角度解釋告終論一:某個ViewGroup一旦決定攔截(ACTINON_DOWN: onInterceptTouchEvent的返回值爲true),那麼這一個事件序列都只能由它來處理(若是事件序列可以傳遞給它的話),而且它的onInterceptTouchEvent不會再被調用
。
這裏咱們也按照MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP事件的順序分析。
part1:
// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
複製代碼
當前爲ACTION_DOWN事件,因此會走if內部邏輯,會依次調用cancelAndClearTouchTargets
和resetTouchState
方法。
上述這兩個方法內部都會調用clearTouchTargets
方法,該方法會將mFirstTouchTarget置爲null。
接下來看part2:
// 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 {
intercepted = true;
}
複製代碼
先看外層的if判斷,咱們此次是ACTION_DOWN事件,因此就會走if內部邏輯。
咱們再看if爲true時的內部邏輯: 咱們disallowIntercept的值默認爲false,因此就會調用子if內部的邏輯,會調用咱們的onInterceptTouchEvent
方法,該方法默認返回值爲false,表示不攔截。
因此咱們最終的intercepted的值爲false。
接着往下看,part3:
// 咱們intercepted的值我爲false,因此會走下面的if內部邏輯。
if (!canceled && !intercepted) {
...
// 咱們是ACTION_DOWN事件,因此下面的條件也知足
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
final int childrenCount = mChildrenCount;
// 咱們的MyLinearLayout是有子View的,因此下面的條件也知足
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
// 開啓for循環,依次遍歷全部子View
for (int i = childrenCount - 1; i >= 0; i--) {
...
// 調用dispatchTransformedTouchEvent方法進行事件分發。
// dispatchTransformedTouchEvent方法若是返回true,表示事件被消費掉了。
// 這裏的dispatchTransformedTouchEvent方法返回值爲false,表示子View不處理事件。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
// 在addTouchTarget方法內部,會給mFirstTouchTarget賦值。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
...
}
...
}
}
複製代碼
上述代碼能夠看出,在dispatchTransformedTouchEvent方法內部,調用了child.dispatchTouchEvent方法,將事件傳遞給child。
而在咱們的例子中,MyLinearLayout的子View爲MyTextView,咱們知道TextView的onTouchEvent方法默認返回的是false,因此咱們這裏的dispatchTransformedTouchEvent方法的返回值爲false。
因此咱們這裏沒有子View處理事件,咱們的mFirstTouchTarget的值爲null。
接着看part4:
// Dispatch to touch targets.
// mFirstTouchTarget的值爲空,因此會走if內部的邏輯,會再次調用dispatchTransformedTouchEvent方法,不過此次的child參數爲null。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
複製代碼
上面分析過,dispatchTransformedTouchEvent方法中的child參數若是爲null的話,就會調用當前View#dispatchTouchEvent方法,進而調用View#onToucheEvent方法。
咱們沒有給MyLinearLayout設置OnTouchListener,因此MyLinearLayout的onTouchEvent方法會執行。
因此咱們能夠解釋ACTION_DOWN相關的log了:
MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyTextView dispatchTouchEvent ACTION_DOWN
MyTextView onTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
MyRelativeLayout onTouchEvent ACTION_DOWN
複製代碼
事件傳遞給MyRelativeLayout,先調用MyRelativeLayout#dispatchTouchEvent方法,接着將MyRelativeLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted,而後調用dispatchTransformedTouchEvent方法,將事件分發給子View MyLinearLayout。
在MyLinearLayout內部也是先調用MyLinearLayout#dispatchTouchEvent方法,接着將MyLinearLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted(part2部分
),而後調用dispatchTransformedTouchEvent方法(part3部分
),將事件分發給子View MyTextView。
在MyTextView內部也是先調用MyTextView#dispatchTouchEvent方法,接着會調用MyTextView#onTouchEvent方法,由於MyTextView繼承自TextView,其CLICKABLE
和LONG_CLICKABLE
屬性默認均爲false,因此MyTextView#onTouchEvent方法返回值爲false。表示事件沒有被MyTextView消費掉,因此會向上傳遞給父View。
接着事件會向上傳遞給MyLinearLayout(part4部分
),調用super.dispatchTouchEvent,也就是View#dispatchTouchEvent方法,在該方法內部會調用onTouchEvent方法。不過跟MyTextView同樣,這個onTouchEvent方法也返回false。因此事件會繼續向上傳遞。
接着事件會向上傳遞給MyRelativeLayout,該過程和MyLinearLayout過程同樣。
總結一下,因爲沒有子View消耗掉ACTION_DOWN事件,當前的mFirstTouchTarget值爲null。
接下來分析ACTION_MOVE事件傳遞的過程,仍是ViewGroup#dispatchTouchEvent方法:
part1:
// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
複製代碼
當前爲ACTION_MOVE事件,因此不會走if內部邏輯。
接着看part2:
// 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_MOVE事件,且通過ACTION_MOVE事件後,mFirstTouchTarget的值依然爲null,因此外層if的條件不知足,直接會走else分支,此時intercepted的值爲true。
因爲不走if分支,因此咱們的onInterceptTouchEvent方法也不會被再次調用。
接下來看part3:
if (!canceled && !intercepted) {
...
}
複製代碼
if條件語句也不知足,內部邏輯不會走,也就不會下發事件給子View了。
再往下看part4:
// 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);
} else {
...
}
複製代碼
因爲咱們的mFirstTouchTarget的值爲null,因此會調用這個dispatchTransformedTouchEvent方法。因爲這裏傳遞的child參數爲null,因此實際上調用的事當前ViewGroup的onTouchEvent方法。
能夠看出ACTION_MOVE事件並無向下分發給子View,而是在調用當前View的onTouchEvent方法後,統一交給父View處理了。
咱們知道事件來源於DecorView
,它繼承自FrameLayout,因此這裏的ACTION_MOVE事件默認就交給DecorView處理了,沒有向下下發,因此咱們的三個自定義View沒有收到出ACTION_DOWN之外的事件。
這種狀況下ACTION_UP的分析與ACTION_MOVE徹底相同,這裏再也不贅述。
至此相關源碼已經分析完畢,有興趣的同窗,能夠寫一個自定義的Button,替換掉MyTextView,看下現象是否是有所不一樣,也能夠嘗試從源碼角度解釋。
一、Android開發藝術探索(Android開發神書,不解釋)