說明
具體流程如圖所示,
對于
dispatchTouchEvent
,onTouchEvent
返回 true 就是自己消費了调卑,返回 false 就傳到父View 的onTouchEvent
方法ViewGroup 想把事件分發(fā)給自己的
onTouchEvent
,需要在onInterceptTouchEvent
方法中返回 true 把事件攔截下來ViewGroup 的
onInterceptTouchEvent
默認不攔截园爷,所以super.onInterceptTouchEvent() = false
View(這里指沒有子View)沒有攔截器,所以 View 的
dispatchTouchEvent
的super.dispatchTouchEvent(event)
默認把事件分發(fā)給自己的onTouchEvent
源碼解析(以下內容部分來自書籍《Android開發(fā)藝術探索》)
Activity對點擊事件的分發(fā)過程
- 當一個點擊事件發(fā)生時,事件首先傳遞給當前 Activity ,由 Activity 的
dispatchTouchEvent()
來進行事件的分發(fā)毕荐,具體的工作是由 Activity 內部 Window來完成倾贰。Window 會將事件傳遞給 decor view, decor view 一般就是當前 Activity 的頂層 View冕碟, 源碼如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- 事件交給 Activity 所附屬的 Window 進行分發(fā), 如果返回 true 整個循環(huán)就結束了匆浙,返回 false 就表示沒有人要處理安寺,交由 Activity 的
onTouchEvent
處理;可知 Activity 調用getWindow().superDispatchTouchEvent(ev)
把事件分發(fā)給ViewGroup, 所以我們來看
Windows#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event)
- 可以看出 Window 是個抽象類首尼,且 Window 唯一實現(xiàn)的是 PhoneWindow挑庶,所以接下來我們看看 PhoneWindow 怎么處理該方法的
PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
- 到這里清晰言秸, 是在 PhoneWidow 將事件傳遞給了 DecorView, 至于什么是 DercorView ,看源碼里如何解釋的:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
這個 mDecor 顯然就是getWindow().getDecorView()
所返回的 View迎捺,而我們通過 setContentView
設置的 View 就是是它的一個子 view井仰。目前事件傳遞到了 DecorView 這里,由于 DecorView 繼承自 FrameLayout 且又是父 View破加,所以最終事件會傳遞給 我們所設置setContentView
的頂級 View 一般來說都是 ViewGroup(不傳遞給他怎么響應用戶點擊事件呢??)
ViewGroup 對點擊事件的分發(fā)
- ViewGroup 對點擊事件的分發(fā)過程主要實現(xiàn)在 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;
}
-
可以看出 ViewGroup 在兩種情況下會判斷是否要攔截當前事件:
-
事件類型 為
ACTION_DOWN
合是,這個很好理解 -
或者
mFirstTouchTarget != null
, 這個從后面代碼可以看出锭环,當事件由 ViewGroup 的子元素成功處理時聪全,mFirstTouchTarget
就會被賦值并指向子元素
-
事件類型 為
且當
ACTION_MOVE
和ACTION_UP
事件到來時,由于actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
這個條件為 false 辅辩,將導致 ViewGroup 的onInterceptTouchEvent
不被調用难礼,并且同一序列中的其他事件都會默認交給他處理。-
但是這里有個特殊情況玫锋,那就是
FLAG_DISALLOW_INTERCEPT
標記尿招,這個標記是子 View 通過requestDisallowInterceptTouchEvent
方法來設置李命。一旦設置了該標記棠涮,ViewGroup 將無法攔截除了ACTION_DOWN
以外的事件盟蚣。為什么說除了ACTION_DOWN
以外的事件,這點從源碼也可以看出:因為在 ViewGroup 在事件分發(fā)時节沦,如果是ACTION_DOWN
就會重置FLAG_DISALLOW_INTERCEPT
標記键思,將導致子View 設置的這個標記失效。因此 當事件為ACTION_DOWN
時 ViewGroup 總是會調用自己的onInterceptTouchEvent
來詢問是否攔截事件甫贯。
我們看看上面代碼的前一句代碼就明白了:
代碼位置// 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(); }
- 從上面代碼可以看出吼鳞, ViewGroup 在
ACTION_DOWN
事件時會做重置操作:會在resetTouchState()
對FLAG_DISALLOW_INTERCEPT
標記進行重置,因此子 View調用requestDisallowInterceptTouchEvent
方法并不能影響 ViewGroup 對ACTION_DOWN
事件的處理
- 從上面代碼可以看出吼鳞, ViewGroup 在
-
從上面分析我們可以總結出兩點:
-
onInterceptTouchEvent
不是每次事件都會被調用叫搁,如果我們想在當前的 ViewGroup 處理所有的點擊事件赔桌,就要選擇onInterceptTouchEvent
方法中處理,只有這個方法能確保每次都被調用 -
FLAG_DISALLOW_INTERCEPT
給我們提供了另一種思路去解決滑動沖突的方法:在子 View 攔截處理
-
接下來我們看 ViewGroup 怎么把事件傳遞給子 View的:
代碼位置
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);
}
-
如上面源碼所示常熙,首先操作的是遍歷 ViewGroup 的所有子元素纬乍,然后判斷是否能夠接收到點擊事件碱茁。是否能夠接收點擊事件主要有兩點判斷:
- 子元素是否在播放動畫
- 點擊事件的坐標是否在子元素坐標區(qū)域內
可以看到他調用了dispatchTransformedTouchEvent
方法來傳遞事件裸卫,所以我們來看看該方法
ViewGroup#dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ........ }
- 可以看到在該方法里如果 傳遞的 child 不是 null 他會直接調用的是子元素的
dispatchTouchEvent
方法來把事件傳遞給子元素
-
再回到 遍歷 ViewGroup 的所有子元素的方法中,可以看到在循環(huán)的最后過程中纽竣,判斷如果子元素的
dispatchTouchEvent
返回 true 墓贿,那么這個 ViewGroup 就暫時不考慮事件在子元素內部是怎么分發(fā)的茧泪,而且mFirstTouchTarget
就會被賦值同時跳出 for 循環(huán),如下所示:
代碼位置newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break;
- 在這里完成了
mFirstTouchTarget
的賦值并且終止了對子元素的遍歷聋袋。其實對mFirstTouchTarget
的賦值是在addTouchTarget
方法里完成的:
ViewGroup#addTouchTarget
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
-
mFirstTouchTarget
其實是一種單鏈表結構队伟,他是否被賦值將直接影響到 ViewGroup 對事件的攔截策略,若果mFirstTouchTarget
為 null 幽勒,那么 ViewGroup 就默認攔截接下來同一序列中所有的點擊事件
- 在這里完成了
-
如果遍歷所有的子元素后事件都沒有被合適處理嗜侮,這里包含兩種情況:
- ViewGroup 沒有子元素
- 子元素處理了點擊事件,但是在
dispatchTouchEvent
中返回了 false (這一般是因為子元素在onTouch
中返回了false)
在這兩種情況下 ViewGroup 會自己處理點擊事件:
// 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); }
View對點擊事件的分發(fā)過程
-
View 對點擊事件的處理稍微簡單點啥容,首先看他的
dispatchTouchEvent
方法
View#dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent event) { ...... 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; } }
- 由于 View 不包含子元素锈颗,所以他無法傳遞事件只能自己處理。從上面的源碼可以看出咪惠,View 對事件的處理首先會判斷有沒有設置
OnTouchListener
如果設置了且OnTouchListener
中的onTouch
放回 true击吱,那么 View 的onTouchEvent
就不會被調用。由此可見OnTouchListener
的優(yōu)先級高于onTouchEvent
- 由于 View 不包含子元素锈颗,所以他無法傳遞事件只能自己處理。從上面的源碼可以看出咪惠,View 對事件的處理首先會判斷有沒有設置
-
接著我們再看
onTouchEvent
的實現(xiàn)遥昧。先看當 View 處于不可用狀態(tài)下的點擊事件處理過程:
View#onTouchEventif ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); }
- 很明顯:不可用狀態(tài)下的 View 照樣會消耗點擊事件覆醇,盡管它看起來不可用
-
接著
onTouchEvent
,如果 View 設置有代理炭臭,那么還會執(zhí)行TouchDelegate
的onTouchEvent
方法if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
-
再看
onTouchEvent
中對點擊事件的具體處理:if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: 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) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } ..... } break; } .... return true; }
- 可以看出只要 View 的
CLICKABLE
和LONG_CLICKABLE
有一個為 true永脓,那么他就會消耗這個事件,即onTouchEvent
返回 true鞋仍,不管他是不是DISABLE
狀態(tài)憨奸。然后在ACTION_UP
發(fā)生時會觸發(fā)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); return result; }
- 若果 View 設置了
OnClickListener
,那么performClick()
方法內部會調用它的onClick
方法
- 可以看出只要 View 的
End. 到此就結束了