聲明:本文內(nèi)容依據(jù)《Android開發(fā)藝術(shù)探索》的思路赂鲤,基于 API 26 進行總結(jié)
一霜定、View 事件分發(fā)機制概覽
1.1 點擊事件傳遞規(guī)則
定義:所謂點擊事件的事件分發(fā)漓滔,其實就是對 MotionEvent 事件的分發(fā)過程锈津。即當(dāng)一個 MotionEvent 產(chǎn)生之后藏姐,系統(tǒng)需要把這個事件傳遞給一個具體的 View隆箩。
關(guān)于MotionEvent對象的含義及參數(shù)可以參考上一篇文章。
點擊事件的分發(fā)由三個重要的方法共同完成:
-
public boolean dispatchTouchEvent(MotionEvent ev) (dispatch:處理)
用來進行事件的分發(fā)羔杨。如果事件能夠傳遞給當(dāng)前 View 捌臊,一定會調(diào)用此方法,返回結(jié)果受當(dāng)前 View 的 onTouchEvent 和下級 View 的 dispatchTouchEvent 方法的影響兜材,表示是否消費當(dāng)前事件理澎。 -
public boolean onInterceptTouchEvent(MotionEvent event) (Intercept:攔截)
用來判斷是否攔截某事件,如果當(dāng)前 View 攔截了某事件曙寡,那么在同一事件序列當(dāng)中糠爬,此方法不會被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件举庶。 -
public boolean onTouchEvent(MotionEvent event)
在 dispatchTouchEvent 方法中調(diào)用执隧,用來處理點擊事件,返回結(jié)果表示是否消費當(dāng)前事件户侥,如果不消費镀琉,則在同一個事件序列中,當(dāng)前 View 無法再次接收到事件蕊唐。
偽代碼表明三個方法的關(guān)系:
// 事件傳遞其實是MotionEvent對象的傳遞
public boolean dispatchTouchEvent(MotionEvent event){
// 標(biāo)記是否消費事件
boolean consume = false;
// 判斷當(dāng)前 View 是否攔截此事件屋摔,攔截則進一步處理
if(onInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else {
// 不攔截則傳遞到子 View 接著判定
consume = child.dispatchTouchEvent(event);
}
return consume;
}
View 設(shè)置 OnTouchListener,回調(diào) onTouch 方法替梨,如果 onTouch 返回 false凡壤,則當(dāng)前 View 的 onTouchEvent 方法會被調(diào)用,如果 true耙替,則被攔截亚侠。說明 OnTouchListener 優(yōu)先級高于 onTouchEvent 。
在 onTouchEvent 方法中俗扇,如果當(dāng)前設(shè)置的有 OnClickListener硝烂,那么它的 onClick 方法會被調(diào)用。OnClickListener 優(yōu)先級最低铜幽,處于事件傳遞的尾端滞谢。
1.2 點擊事件傳遞順序
順序: Activity --> ViewGroup --> View串稀。
這里借用一張圖來說明:
簡單解釋一下這張圖:
- 三個重要角色 Activity、ViewGroup狮杨、View
- 三個角色包含兩個重要函數(shù):dispatchTouchEvent() 和 onTouchEvent()母截,ViewGroup 多了一個 onInterceptTouchEvent() 用來判斷是否攔截當(dāng)前事件
- dispatchTouchEvent() 用來傳遞點擊事件,onInterceptTouchEvent() 用來判斷是否攔截當(dāng)前事件橄教,onTouchEvent() 用來處理是否消費當(dāng)前事件
- 如果 dispatchTouchEvent() 返回 false 則傳遞給上級 View 或 Activity清寇,如果 true 則傳遞事件
- 如果某 View 的 onTouchEvent 返回 false,則它的父容器的 onTouchEvent 會被調(diào)用护蝶,依此類推华烟。如果都返回 false,交給 Activity 處理持灰。
二盔夜、基于 Android 8.0 源碼分析View 事件分發(fā)機制
2.1 事件最先傳遞給 Activity,由其 dispatchTouchEvent 進行派發(fā)
也就是上面流程圖的第一部分
android.app.Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 空方法
onUserInteraction();
}
// 事件交給 Activity 所屬的 Window 進行分發(fā)堤魁,返回 true 事件結(jié)束
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果沒有 View 處理事件栈拖,調(diào)用 Activity 的 onTouchEvent
return onTouchEvent(ev);
}
2.2 Window 會將事件傳遞給 decor view刷喜,一般是當(dāng)前頁面的底層容器(setContentView 所設(shè)置的 View 的父容器)通過 Activity.getWindow.getDecorView() 可以獲得删豺。
(1)Window 是一個抽象類谦屑,其 superDispatchTouchEvent 也是一個抽象方法。所以要找到其實現(xiàn)類涛漂。
通過 Window 源碼描述可知其實現(xiàn)類為 PhoneWindow赏表,描述大意為:Window 類可以控制頂級 View 的外觀和行為策略,它的唯一實現(xiàn)位于 android.view.PhoneWindow 中匈仗,當(dāng)你要實例化這個 Window 類的時候瓢剿,你并不知道它的細節(jié),因為這個類會被重構(gòu)悠轩,只有一個工廠方法可以使用间狂。
(2)PhoneWindow 類是 Window 唯一的實現(xiàn)類,前者直接將事件傳遞給了 DecorView
PhoneWindow 類 package com.android.internal.policy;
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
PhoneWindow 類的 mDecor 對象
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
...
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
通過 ((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
可以獲取 Activity 所設(shè)置的 View火架,這個 mDecor 就是 getWindow().getDecorView() 返回的 View鉴象,通過 setContentView 設(shè)置的 View 是它的一個子 View。目前事件由 mDecor.superDispatchKeyEvent(event)
傳遞到了 DecorView 這里何鸡。
(3)DecorView 是整個Window界面的最頂層View纺弊,包含通知欄,標(biāo)題欄骡男,內(nèi)容顯示欄(就是 Activity setContentView 設(shè)置的布局 View)三塊區(qū)域
public class DecorView extends FrameLayout
public boolean superDispatchKeyEvent(KeyEvent event) {
...
// 傳遞給父類的 dispatchKeyEvent
return super.dispatchKeyEvent(event);
}
DecorView 的父類為 FrameLayout 且是父 View淆游,事件會一層層傳遞最終會傳遞給 View。到這時,事件已經(jīng)傳遞到頂級 View 了犹菱,也就是在 Activity 中通過 setContentView 設(shè)置的 View拾稳。頂級 View 也叫根 View,一般來說都是 ViewGroup腊脱。
2.3 頂級 View 對點擊事件的分發(fā)過程
點擊事件達到頂級 View (一般是一個 ViewGroup)以后访得,會調(diào)用 ViewGroup 的 dispatchTouchEvent
方法,對應(yīng)流程圖為:
之后的邏輯是這樣的:
如果頂級 ViewGroup 攔截事件即 onInterceptTouchEvent 返回 true陕凹,則事件由 ViewGroup 處理悍抑,這時如果 ViewGroup 的 mOnTouchListener 被設(shè)置,則 onTouch 會被調(diào)用捆姜,否則 onTouchEvent 會被調(diào)用传趾。
在 onTouchEvent 中迎膜,如果設(shè)置了 mOnClickListener泥技,則 onClick 會被調(diào)用。
如果頂級 View 不攔截事件磕仅,則事件會傳遞給子 View 并調(diào)用其 dispatchTouchEvent珊豹,接下來會一直處理到結(jié)束。
package android.view; ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {...} 方法源碼
(1)當(dāng)前 ViewGroup 是否攔截點擊事件
// 檢查攔截榕订。
final boolean intercepted;
// Step 1 判斷按下或者mFirstTouchTarget:
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// Step 2 判斷 mGroupFlags 和 FLAG_DISALLOW_INTERCEPT
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // 動作改變時重置事件狀態(tài)
} else {
intercepted = false;
}
} else {
// 沒有觸摸目標(biāo)且不是最初的按下事件店茶,所以
// 該 ViewGroup 繼續(xù)處理事件
intercepted = true;
}
Step 1 判斷按下或者mFirstTouchTarget:if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)
: 判斷當(dāng)前動作是否為 MotionEvent.ACTION_DOWN 按下,或者 mFirstTouchTarget != null劫恒。mFirstTouchTarget 通過查看后面邏輯可以看出指的是如果事件由 ViewGroup 的子元素成功處理贩幻,則 mFirstTouchTarget 會被賦值并指向子元素。
也就是說两嘴,當(dāng) ViewGroup 不攔截事件并將事件交給子 View 去成功處理丛楚,ViewGroup 不會再處理除 MotionEvent.ACTION_DOWN 以外的事件。因為此時 mFirstTouchTarget 的值不為 null憔辫,同時 MotionEvent.ACTION_MOVE 和 MotionEvent.ACTION_UP 不會再經(jīng)歷 ViewGroup 的 onInterceptTouchEvent(ev)
方法趣些。
Step 2 判斷 mGroupFlags 和 FLAG_DISALLOW_INTERCEPT: mGroupFlags 參數(shù)不用理會。FLAG_DISALLOW_INTERCEPT 標(biāo)記一旦被設(shè)置贰您,ViewGroup 將無法攔截除 ACTION_DOWN 以外的事件坏平。這個標(biāo)記的設(shè)置一般是子 View 通過 ViewGroup 的 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {...}
方法來設(shè)置的。每次按下以后都會清除和重置標(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();
}
這個方法是在第(1)步之前的锦亦,由于每次重新按下都會清除標(biāo)記舶替,所以 ViewGroup 總是能調(diào)用onInterceptTouchEvent
來判斷是否攔截事件。如果事件不為 ACTION_DOWN 且其子 View 設(shè)置了標(biāo)記杠园,ViewGroup 就不會再攔截事件而是直接交給子 View 去處理顾瞪。
結(jié)論:
- 當(dāng) ViewGroup 決定攔截事件,則后續(xù)不會再調(diào)用
onInterceptTouchEvent
方法,因為 Step 1中條件 actionMasked 玲昧!= MotionEvent.ACTION_DOWN 并且 mFirstTouchTarget == null 故而不會再跑里面的方法栖茉。 - FLAG_DISALLOW_INTERCEPT 標(biāo)記的作用是讓 ViewGroup 不再攔截事件,當(dāng)然前提是 ViewGroup 不攔截 ACTION_DOWN 事件孵延÷榔可以利用這個方法去解決滑動沖突。
(2)ViewGroup 不攔截事件向下分發(fā)的過程
if (newTouchTarget == null && childrenCount != 0) {
// Step 1:獲取點擊坐標(biāo)位置
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;
// Step 2:遍歷 ViewGroup 所有子元素
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 如果有一個子 View 可以被點擊并且我們需要它獲得點擊事件尘应,
// 會讓它首先獲得點擊事件惶凝,如果該 View 不處理則會執(zhí)行正常的調(diào)度。
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// Step 3:判斷是否可以接收點擊事件(判斷方法為是否在執(zhí)行動畫)犬钢,
// 或者坐標(biāo)是否坐落在子元素的區(qū)域內(nèi)
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);
// Step 4:進行判斷苍鲜,如果 child 不為 null,則 child 進行 dispatchTouchEvent玷犹。反之混滔,則調(diào)用父 View 的 dispatchTouchEvent。
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();
// Step 5:結(jié)合上方 if 返回 true 表示子元素的 dispatchTouchEvent 處理成功歹颓,
// mFirstTouchTarget 就會被賦值同時跳出 for 循環(huán)
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();
}
Step 1: 只是記錄點擊的坐標(biāo)坯屿。
Step 2: 遍歷 ViewGroup 所有子元素。
Step 3: canViewReceivePointerEvents 該子 View 是否正在播放動畫巍扛。
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
isTransformedTouchPointInView 判斷點擊是否在該子 View 的區(qū)域內(nèi)领跛。
如果子元素滿足這兩個條件,那么事件就會傳遞給它來進行處理撤奸。
Step 4: dispatchTransformedTouchEvent 方法包含下列語句吠昭,也就是根據(jù) child view 是否為 null 來決定是否將事件傳遞給父 View。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
Step 5: addTouchTarget 方法主要是為 mFirstTouchTarget 賦值胧瓜,表面該事件已經(jīng)交給子 View 進行處理矢棚。如果 mFirstTouchTarget 為 null,ViewGroup 就會默認攔截接下來同一序列的所有點擊事件贷痪。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
如果所有的子元素都沒有處理事件幻妓,包含兩種情況:第一是 ViewGroup 沒有子元素,第二是子元素處理了點擊事件劫拢,但是在 dispatchTouchEvent 中返回了 false肉津。在這樣的情況下 ViewGroup 會接著處理:
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 傳遞的第三個參數(shù)為 null舱沧,也就是說會調(diào)用 ViewGroup 父類的 dispatchTouchEvent妹沙。
2.4 View 對點擊事件的處理過程
package android.view; --> View 源碼片段
(1)dispatchTouchEvent 方法
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// Step 1:判斷有沒有設(shè)置 OnTouchListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// Step 2:上方 onTouch 返回 true,result 為 true 則不會調(diào)用 onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
Step 1:首先判斷有沒有設(shè)置 OnTouchListener熟吏,如果設(shè)置了并且 onTouch 返回 true距糖,則標(biāo)記 result 為 true玄窝,說明 onTouch 消費了事件。
Step 2: result 為 true 則不會調(diào)用 onTouchEvent悍引,如果標(biāo)記 result 為 false 并且 onTouchEvent 返回 true恩脂,說明該 View 消費了點擊事件,最后標(biāo)記設(shè)置為 true 并返回告知父 View 事件已經(jīng)被消費了趣斤。
(2)onTouchEvent 方法
首先獲取當(dāng)前 View 的狀態(tài) clickable(是否可點擊)俩块,然后再進行判定如果該 View 的狀態(tài)為 DISABLED(不可用)最后再返回 clickable。有一句重要的注釋:一個可點擊的不可用的 View 依舊會消費點擊事件浓领,它只是不作響應(yīng)而已玉凯。說明不可用的 View 依舊消費點擊事件。
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
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;
}
接下來判定是否設(shè)置了代理联贩,這里的 onTouchEvent 的工作機制看起來和 OnTouchListener 類似漫仆,不深入研究。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
接下來看 onTouchEvent 對點擊事件的具體處理
// 如果 View 可點擊或者可顯示懸浮或長按的工具提示
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 如果是懸浮工具窗的操作就另行處理
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
// 如果 View不可點擊移除各種點擊回調(diào)并跳出
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// 按鈕在釋放之前我們確實顯示了它的點擊效果泪幌。
// 使該按鈕顯示按下的狀態(tài)以確保用戶能夠看到盲厌。
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 通過mHasPerformedLongPress得知這不是長按事件
// 說明這是一個單擊事件,所以移除長按檢測
removeLongPressCallback();
// 如果處于按下狀態(tài)只執(zhí)行點擊操作
if (!focusTaken) {
// 使用一個 Runnable 的 post 方式來調(diào)用 performClick 好過直接調(diào)用座菠。
// 這樣不影響該 View 除了點擊外的其他的狀態(tài)的更新
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// *重要方法狸眼,用來判定是否設(shè)置了 OnClickListener 再進行一系列處理
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
// 處理觸摸回調(diào)
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
經(jīng)過了一系列的判定后來到了一個重要方法 performClick()藤树,這個方法來處理具體的 OnClick 回調(diào)等邏輯操作浴滴。以下是 performClick 方法:
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
// 存在OnClickListener,播放點擊音效
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
到這里點擊事件分發(fā)到了 onClick 函數(shù)岁钓,接下來就是我們自己去 onClick 方法中去實現(xiàn)邏輯操作了升略。
View 的 LONG_CLICKABLE 屬性默認為 false,CLICKABLE 屬性依據(jù)是否可點擊來確定屡限。通過 setLongClickable 和 setClickable 分別可以改變兩個屬性的值品嚣,另外,通過 setOnClickListener 會自動將 View 的 CLICKABLE 屬性設(shè)置為 true钧大,相關(guān)源碼:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
到這里事件傳遞機制就基本結(jié)束了翰撑。
三、總結(jié)
事件傳遞機制的一些結(jié)論:
- 同一事件序列是指手指接觸屏幕到離開屏幕中產(chǎn)生的一系列事件啊央。以 down 開始眶诈,中間一些 move,最后以 up 事件結(jié)束瓜饥。(按下滑啊滑再松開)
- 正常情況下一個事件序列只能被一個 View 攔截且消費逝撬。但是可以通過 onTouchEvent 強行傳遞給其他 View 處理。(這個事我承包了乓土,但是我可以強行給你)
- 某個 View 一旦決定攔截宪潮,那么這一事件序列都只能由它來處理(如果能傳遞給它的話)溯警,并且它的 onInterceptTouchEvent 不會再被調(diào)用。(這事我承包了狡相,不用再問了)
- 某個 View 一旦開始處理事件梯轻,如果不消費 ACTION_DOWN 事件(onTouchEvent 返回 false),那么同一事件序列中其他事件都不會交給他處理尽棕,并且事件重新交給它的父元素去處理檩淋,即父元素的 onTouchEvent 會被調(diào)用。(最開始的事情你都不做萄金,后面的就不給你了蟀悦,交給你上面的人去做)
- 如果 View 不消費除 ACTION_DOWN 以外的其他事件,那么這個點擊事件會消失氧敢,此時父元素的 onTouchEvent 不會被調(diào)用日戈,并且當(dāng)前 View 可以持續(xù)收到后續(xù)事件,最終消失的事件會傳遞給 Activity 處理孙乖。(我只答應(yīng)做開頭的一點事情浙炼,后面可以通知我,也不用告訴我上級唯袄,后面的事情你們自己處理)
- ViewGroup 默認不攔截任何事件弯屈。Android 源碼 ViewGroup 的 onInterceptTouchEvent 默認返回 false。(我恋拷,ViewGroup资厉,小弟多。事情都給下面做)
- View 沒有 onInterceptTouchEvent 方法蔬顾,一旦有點擊事件傳遞宴偿,調(diào)用 onTouchEvent 方法。(View 的具體實現(xiàn)是最底層小弟诀豁,不用問我做不做事窄刘,當(dāng)然做)
- View 的 onTouchEvent 默認都會消費事件(返回true),除非它是不可點擊的 (clickable 和 longClickable 同時為 false)舷胜。View 的 longClickable 屬性默認 false娩践,click 看情況。Button 的 clickable 默認為 true烹骨,TextView 的 clickable 默認為 false翻伺。
- View 的 enable 屬性不影響 onTouchEvent 的默認返回值。哪怕 View 的 enable 為 false展氓,只要它的 clickable 或者 longClickable 有一個為 true穆趴,那么它的 onTouchEvent 就返回 true。
- onClick 發(fā)生的前提是 View 可點擊遇汞,并且收到了 down 和 up 的事件未妹。
- 事件傳遞是由外向內(nèi)的簿废,先傳遞給父元素再由父元素分發(fā)給子 View,通過 requestDisallowInterceptTouchEvent (好長的方法名络它,直譯為:申請不允許攔截點擊事件族檬,也就是干擾父元素消費事件)方法可以干預(yù)父元素的事件分發(fā)過程,但是 ACTION_DOWN 事件除外化戳。