Android 事件分發機制
[TOC]
前言
Android 分發機制是每個 Android 開發者所要必須了解的知識點,了解了分發機制以后就可以很輕松的解決開發中遇到的問題,比如:
- 實現鍵盤彈出時,點擊空白處隱藏鍵盤
- 解決滑動沖突
- 自定義 View 實現仿微信錄音
- 還有一些其他的應用等。
我會根據源碼的方式進行講解,盡量描述的清楚些。
Android 事件分發中的事件是什么?
首先,我們在操作移動設備的時候,大多數的操作都是通過手指在屏幕上點擊、滑動進行的,為什么我們在點擊一個按鈕、滑動一個列表的時候能夠操控頁面呢?
其實這里就是 Android 事件分發機制的體現。
在我們點擊按鈕的時候,會有按下、抬起的操作,其實這兩個操作對應的就是 ACTION_DOWN 事件和 ACTION_UP 事件。
在我們滑動頁面的時候,會有按下、移動、移動。。。移動、抬起的操作,這這就對應事件分發里面的 ACTION_DOWN 事件、ACTION_MOVE、ACTION_MOVE。。。ACTION_MOVE、 ACTION_UP 事件。
不管是上面的點擊還是滑動,在進行的時候,都是構成了一個事件序列,一個事件序列,一般都是從 ACTION_DOWN 開始、 ACTION_UP 結束的。
在 Android 中,這些事件序列被封裝到了 MotionEvent 中。在 MotionEvent 中一般有以下幾種重要的事件:
事件類型 | 觸發條件 |
---|---|
MotionEvent.ACTION_DOWN | 手指接觸到屏幕 |
MotionEvent.ACTION_MOVE | 手指在屏幕上滑動 |
MotionEvent.ACTION_UP | 手指離開屏幕 |
MotionEvent.ACTION_CANCEL | 意外原因導致事件序列終止 |
大體上,就是這四種不同的事件來構成事件分發里面的事件序列。
Android 事件分發是什么?
上面講了關于事件的概念,那么事件到底是怎么分發的呢?
這要從我們應用頁面講起。Android 中的頁面構成是這樣的:
其中 Activity 是在最外面的,而我們平時 setContentView
的布局是在最里面的,而我們布局正常情況下是最外面是 ViewGroup ,里面包裹了 ViewGroup 或者 View 。
比如在登錄頁面,我們點擊了登錄按鈕,那么就完成了一次完整的事件分發。
這個時候事件走向是這樣的
Activity -> PhoneWindow -> DecorView -> ViewGroup -> LinearLayout -> Button
可以看到:事件是從最外面的 Activity 傳遞到最里層的 Button 按鈕的。
其實事件分發就是將我們手指產生的一些事件,傳遞到一個具體的 View 上去并且處理的過程。
為什么會有事件分發機制?
Android 上的View是樹形結構的,View 可能會重疊到一起,當我們點擊的時候,可能會有多個 View 進行響應,事件分發機制主要是解決事件該交給誰去處理的。
比如上面,我們點擊了 Button,但是 Button 是在 LinearLayout 里面的,那我們點擊 Button 的時候,到底是 交給 LinearLayout 處理還是 Button 處理,由事件分發機制說了算。
還有就是 Android 中的滑動沖突等,都要需要依據事件分發機制去解決。
事件分發里面重要的三個方法
- dispatchTouchEvent()
在 Activity 、 ViewGroup、 View 中都是有這個方法的,事件接收是從 Activity 的 dispatchTouchEvent() 開始的。 - onInterceptTouchEvent()
這個方法是 ViewGroup 獨有的,在 ViewGroup 中可以通過這個方法確定是否攔截該事件,如果攔截,那么就由 ViewGroup 的 onTouchEvent() 方法接管事件序列。這個方法默認是返回 false 的,也就是默認不攔截事件 - onTouchEvent()
這個方法也是在 Activity 、 ViewGroup 、 View 中都有的,如果如果確定在 Activity、ViewGroup、View 中處理事件,一般是在這個方法處理的。
事件分發講解
先看 Activity 的。
Activity 的事件分發
前面講到事件序列是從最外層的 Activity 開始接收的,然后依次分發到具體的 View 中的,那就先從最外層的 Activity 層開始看起,也就是從 Activity 的 dispatchTouchEvent() 方法看起。
public boolean dispatchTouchEvent(MotionEvent ev) {
// 首先當我們每次手指觸控屏幕的時候,都會去調用 onUserInteraction() 方法,
// 如果你想知道用戶用某種方式和你正在運行的 activity 交互,可以重寫此方法。
// 因為在每次事件分發的時候都會調用到該方法。
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可以看到會先調用 getWindow().superDispatchTouchEvent(ev)
方法,來看下這個 getWindow() 是什么:
public Window getWindow() {
return mWindow;
}
這里返回的是一個 Window 對象,在 Android 里面的 Window 是一個抽象類,唯一的實現是 PhoneWindow 類。也就是調用的 PhoneWindow 的 superDispatchTouchEvent(ev) 方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// 這里調用了 mDecor 的 superDispatchTouchEvent 方法
return mDecor.superDispatchTouchEvent(event);
}
// mDecor 是一個DecorView對象
private DecorView mDecor;
public boolean superDispatchTouchEvent(MotionEvent event) {
// 調用父類的 super.dispatchTouchEvent 方法
return super.dispatchTouchEvent(event);
}
// DecorView 繼承于 FrameLayout
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {}
// FrameLayout 繼承于ViewGroup
public class FrameLayout extends ViewGroup {}
可以看到,DecorView 是繼承于 FrameLayout 的,FrameLayout是繼承于 ViewGroup 的,所以最終調用了 ViewGroup 的 dispatchTouchEvent() 方法
這個時候,事件已經被傳遞到了 ViewGroup 。也就是說如果 getWindow().superDispatchTrackballEvent(ev) 這行代碼返回的是 true ,表示 事件被消費掉了,那么本次事件分發就結束了。直接 return 。不會執行到
return onTouchEvent(ev);
也就是不會執行 Activity 的 onTouchEvent 方法,如果是getWindow().superDispatchTrackballEvent(ev) 返回的 false,那么就表示ViewGroup 和 View 均沒有對事件進行處理,調用 Activity 的 onTouchEvent 方法。
上面已經講了 onInterceptTouchEvent() 是只存在 ViewGroup 中的,上面的源碼也驗證了這一點。下面看看 ViewGroup 的 dispatchTouchEvent() 方法
ViewGroup 的事件分發。
ViewGroup 的事件分發,也是從 dispatchTouchEvent() 開始的,來看下關鍵代碼:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略n行代碼。。。
// Handle an initial down.
// 這里在每次事件是 MotionEvent.ACTION_DOWN 的時候,調用 resetTouchState()
// 因為事件序列是從 down 事件開始的,所以每次接收到 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();
}
// Check for interception.
final boolean intercepted;
// mFirstTouchTarget 在子 View 接管事件的時候會賦值,否則為 null
// 如果是 ACTION_DOWN 事件,或者 mFirstTouchTarget 不為空,表明ACTION_DOWN事件沒有被消費,
// 走 ViewGroup 的分發流程,
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 是否允許攔截 disallowIntercept = 是否禁用事件攔截的功能(默認是false),
// 可通過調用 requestDisallowInterceptTouchEvent()修改 FLAG_DISALLOW_INTERCEPT這個標志位
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 如果不允許攔截,就會 執行 onInterceptTouchEvent(ev)
if (!disallowIntercept) {
// 這里會根據 onInterceptTouchEvent 的返回值判斷當前的 ViewGroup 是否進行攔截.
// onInterceptTouchEvent(ev)的源碼顯示,默認是會返回 false 的,如果有需要我們可以返回 true
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
// ViewGroup 默認是不攔截的,所以置為 false
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
// 如果不是 Down 事件,并且沒有子 View 接管事件,那么 ViewGroup 會阻止后面的事件向后傳遞
// intercepted置為 true ,攔截事件序列,不需要調用onInterceptTouchEvent(ev),自己處理
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
// 如果攔截,或者一個 View 接管了該事件序列,那么就走正常的分發流程
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 如果沒有觸發取消事件并且沒有攔截
if (!canceled && !intercepted) {
//省略部分代碼...
// for 循環遍歷 View 注意,這里是倒敘的,也就是從最里面的 View 進行遍歷
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;
}
// 檢查能不能接收到事件,檢查觸摸位置是否在View區域內,并且在不在播放動畫
// 如果都不滿足,執行 continue 繼續循環下個 View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 看是否有 View 接管,如果有,跳出循環。
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);
// 這里進行處理,如果返回 true ,那么表示有 View 接管了 事件,那么就給 newTouchTarget
// 在 addTouchTarget(child, idBitsToAssign)中賦值,View接管該事件。結束循環完成分發。
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);
}
}
// 省略n行代碼。。。
}
可以看到,在執行 dispatchTouchEvent 的開始,如果是 ACTIOPN_DOWN 的話,調用 resetTouchState() 來重置所有的觸摸狀態,這里會將 mFirstTouchTarget 設置為 null 。然后準備新的周期,這樣做主要是因為事件序列是從 down 事件開始的,所以每次接收到 down 事件,就是一個新的事件序列。要重新開始處理。
然后執行
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 是否允許攔截 disallowIntercept = 是否禁用事件攔截的功能(默認是false),
// 可通過調用 requestDisallowInterceptTouchEvent()修改 FLAG_DISALLOW_INTERCEPT這個標志位
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 如果不允許攔截,就會 執行 onInterceptTouchEvent(ev)
if (!disallowIntercept) {
// 這里會根據 onInterceptTouchEvent 的返回值判斷當前的 ViewGroup 是否進行攔截.
// onInterceptTouchEvent(ev)的源碼顯示,默認是會返回 false 的,如果有需要我們可以返回 true
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
// ViewGroup 默認是不攔截的,所以置為 false
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
// 如果不是 Down 事件,并且沒有子 View 接管事件,那么 ViewGroup 會阻止后面的事件向后傳遞
// intercepted置為 true ,攔截事件序列,不需要調用onInterceptTouchEvent(ev),自己處理
intercepted = true;
}
這里會判斷是不是 DOWN 事件或者是 mFirstTouchTarget 不是 null。mFirstTouchTarge != null 也就是已經找到能夠接收 touch 事件的 View。這個時候會進入 if 內部。
在內部會先創建 disallowIntercept (禁止攔截) 標志位。這個標志位可以在 子 view 中使用 requestDisallowInterceptTouchEvent(boolean disallowIntercept) 方法去設置。以便請求父 View 不攔截事件。
如果 disallowIntercept = false ,也就是在子 view 執行 requestDisallowInterceptTouchEvent(false)
也就是請求攔截,那么就會執行 onInterceptTouchEvent(ev); 方法去判斷當前的 ViewGroup 是否對該事件進行攔截。
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
onInterceptTouchEvent 方法內部前面的是對一些特殊事件的處理,就先不管了,如果不滿足上面的四個條件的話,就會直接返回 false ,也就是 ViewGroup 默認是不攔截的。所以這個時候的 intercepted 值為 false 不攔截;
如果 disallowIntercept = false ,也就是在子 view 執行 requestDisallowInterceptTouchEvent(true)
也就是請求不攔截,那么ViewGroup 就不會去走 onInterceptTouchEvent 方法,直接將 intercepted 值置為 false,表示不攔截。
我們看到最后一個 else 把 intercepted 的值直接置為 true,表示攔截,那么是什么時候觸發的呢?
也就是說在 當前傳遞來的事件不是 DOWN 并且沒有 View 接管事件的時候,ViewGroup 默認是進行攔截的。很好理解,事件的開端沒有子 View 進行處理,那么之后的事件也不會交給子 View 去處理了。
再往后的話就是通過一個循環,倒序遍歷所有的子 View,依次判斷能不能接收到事件或者是正在播放動畫,然后滿足的話依次去執行 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
。
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.
// 如果 取消,或者 事件為 ACTION_CANCEL,不做任何過濾或轉換
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
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;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (!child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// 如果有子 View ,那么去調用子 View 的 dispatchTouchEvent 去處理該事件。也就
//把事件傳遞到了下一級
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
在 dispatchTransformedTouchEvent 內部可以看到,如果是 child 不為 null 的時候,調用子 View 的 dispatchTouchEvent 去處理,這樣就把把事件交給子 View 去處理,需要注意的是:這個地方的返回值 handled 是 最后子 View 的 onTouchEvent 的返回值。
如果返回值為 true ,那么表示子 View 消費掉了事件,那么就會在ViewGroup 的 dispatchTouchEvent 方法的內部通過:
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
對newTouchTarget 進行賦值。在 addTouchTarget 內部對 mFirstTouchTarget 進行賦值。
如果返回的是 false,那么就跳過 if 內部。最終會執行到下面的代碼:
(這里的代碼是 ViewGroup 的 dispatchTouchEvent(event) 方法的一部分)
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 沒有 View 處理,那么交給自己處理,在 View 的 dispatchTouchEvent方法中調用
// onTouchEvent,執行 if (child == null) {
// handled = super.dispatchTouchEvent(event);
// }
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
// 走到這里說明了,已經把事件交給了具體的 View 處理,那么會將MotionEvent.ACTION_DOWN
// 的后續事件分發給mFirstTouchTarget指向的View去處理
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 該TouchTarget已經在前面的情況中被分發處理了,避免重復處理
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 如果被當前ViewGroup攔截,向下分發cancel事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// dispatchTransformedTouchEvent()方法成功向下分發取消事件或分發正常事件
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;
}
}
這里會判斷有沒有 View 處理事件。
如果沒有 View 處理,就調用 dispatchTransformedTouchEvent 方法,傳入 child = null,執行 handled = super.dispatchTouchEvent(event);
這個時候 ViewGroup super 的 dispatchTouchEvent方法,也就是調用 View 的 dispatchTouchEvent 去處理,進而在View 的 dispatchTouchEvent 中調用 onTouchEvent。這個時候調用的 onTouchEvent就是 ViewGroup 內部的 onTouchEvent 方法。
如果有 View 處理,那么就把后續事件交給子 View 繼續處理。這個時候,事件就傳遞到了 View 里面。
這里有個點就是:
如果在子 View 中的 onTouchEvent 返回了 true,那么 mFirstTouchTarget 就不為空,就輪不到父 View 處理。
如果在子 View 中的 onTouchEvent 返回了 false,子 View 不處理事件,那么 mFirstTouchTarget 就為空,就需要父 View 處理。
View 的事件分發
其實 View 的分發機制就比較簡單了,View 的分發機制也是從 dispatchTouchEvent(event) 開始的:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 可以看到,比如滿足四個條件,才會消費事件。
//1、ListenerInfo 不為 null
//2、mOnTouchListener 不為 null
//3、View 的 enable 是可用的
//4、onTouch 方法返回的是 true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 從上面的代碼可以看到,是先執行了 onTouch 方法的,如果在 onTouch 方法
// 里面已經把事件消費掉,那么久不會執行 onTouchEvent 方法,如果沒消費,
// 就判斷是否在 onTouchEvent 中消費掉,如果消費掉,也是直接返回。如果沒沒消費,繼續向下執行
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
再來看下 View 的 onTouchEvent :
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 判斷view 是可點擊的
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 如果 View 可點擊,但是處于不可用狀態,仍然會消費掉事件。
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;
}
// 如果 View 設置了代理,會調用代理的 onTouchEvent 方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 如果 View 是可用的,是可點擊的,并且沒有被 onTouchListener 的 onTouch 方法
// 消費事件,那么就會執行到 點擊事件 performClick()
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick();
...
break;
return true;
}
return false;
}
從上面的源碼分析可以看出,View 的 onTouchListener 優先級高于 onTouchEvent 的優先級,onTouchEvent 的優先級高于 onClickListener onClick 的優先級。
并且從源碼中也看到了,如果我們設置了 View 是 disabled 的,那么也就沒有下面的 onTouch,因為 && 判斷的時候,前面為 false,后面就不去判斷,所以 onTouch 就不會執行。但是不會影響執行 onTouchEvent。
在 onTouchEvent 的內部,如果得到 View 是 disabled 的,就會返回 View 是不是 clickable 或者 longClickable 的,如果有一個是 true ,就消費掉該事件。不會執行onCLick 事件了。
onTouch 執行條件
- 父 ViewGroup 沒有攔截事件
- 當前 View 是可可用的
onClick 執行的條件如下:
- 父 ViewGroup 沒有攔截事件
- 當前 View 是可可用的
- 當前 View 是可點擊的
- 當前 View 內部對 ACTION_UP 事件處理
- onTouch不消費事件
為了便于理解,可以參考這張圖:
關于事件分發的總結
- 事件分發里面的事件通常指 ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_Cancel 這四種,他們連在一起就構成了一個時間序列。比如 ACTION_DOWN、ACTION_MOVE、ACTION_MOVE…ACTION_MOVE、ACTION_UP,這就構成了一個事件序列。
- 事件序列的傳遞是從 Activity 開始,依次經過、PhoneWindow、DecorView、ViewGroup、View。如果是最終的 View 也沒有處理的話,就依次向上移交,最終會在 Activity 的 onTouchEvent 方法中處理。
- 如果事件從 ViewGroup 中傳遞給 View 去處理的時候,如果 View 沒有處理掉,在 onTouchEvent 方法中返回了 false,那么該事件就重新交給 ViewGroup 處理,并且后續的事件都不會再傳遞給該 View。
- onInterceptTouchEvent 方法只有在 ViewGroup 中存在,并且默認返回 false,代表 ViewGroup 不攔截事件。
- 正常情況下,一個事件序列只能由一個 View 處理。如果一個 View 接管了事件,不管是具體的子 View還是 ViewGroup,后續的事件都會讓這個 View 處理,除非人為干預事件的分發過程。
- 子 View 可以通過調用 requestDisallowInterceptTouchEvent(true) ,干預父元素的除了 ACTION_DOWN 事件以外的事件走向。 一般用于處理滑動沖突中,子控件請求父控件不攔截ACTION_DOWN以外的其他事件,ACTION_DOWN事件不受影響。
- View 的 onTouchEvent 方法默認是返回 true 的,也就是會默認攔截事件。除非 它是不可點擊的,(clickable、longClickable 都為 false)。View 的 longClickable 默認均為 false,Button、ImageButton 的 clickable 默認為 true,TextView clickable 默認為false
- View 的 enable 屬性不會影響 onTouchEvent 的返回值,只要 clickable、longClickable 有一個為 true,那么onTouchEvent就默認會返回 true
- View 的點擊事件是在 ACTION_UP 事件處理的時候執行的,所以要執行,必須要有 ACTION_DOWN 和 ACTION_UP 兩個事件。