從View的事件分發(一)分發流程文章得知,從用戶點擊屏幕到屏幕中的控件響應操做的大體流程是 Activity-> ViewGroup-> View。那麼 Activity 是怎麼把事件傳遞給咱們在 xml 中寫的根視圖呢?就是說 Activity-> ViewGroup 具體是一個怎樣的流程?java
當一個點擊操做發生時,最早傳遞給 當前 Activity,由 Activity 的 dispatchTouchEvent 方法分發。咱們看下 Activity 的 dispatchTouchEvent 的具體源碼:android
//Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//調用 window 的 superDispatchTouchEvent 方法進行事件的分發
//若是返回 true,就表明事件被消費, Activity 的 dispatchTouchEvent 就直接返回 true,再也不往下執行。
//若是返回 false,就表明事件沒有被消費,就調用 Activity 的 onTouchEvent 方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
繼續查看 window 的 superDispatchTouchEvent 源碼,window 是一個抽象類,有一個惟一的實現類 PhoneWindow(這一點能夠在 window 的類註釋中查看到),那麼咱們就找 PhoneWindow 的 superDispatchTouchEvent 方法。ide
//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor 是 DecorView
//這裏調用 DecorView 的 superDispatchTouchEvent 方法
return mDecor.superDispatchTouchEvent(event);
}
複製代碼
繼續查看 DecorView 的 superDispatchTouchEvent 方法源碼分析
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
//調用父類的 dispatchTouchEvent 方法進行分發.
return super.dispatchTouchEvent(event);
}
複製代碼
咱們知道能夠經過 ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
來找到 Activity 所設置的 View,PhoneWindow 中調用的 mDecor 就是 getWindow().getDecorView() 獲取的 DecorView,咱們經過 Activity 所設置的 View 就是它的子 View。DecorView 是繼承的 FrameLayout,固然 FrameLayout 是沒有重寫 dispatchTouchEvent 方法的,因此最終就是經過 FrameLayout 的父類 ViewGroup 的 dispatchTouchEvent 方法來將事件分發給咱們開發的頁面。post
完整的事件分發流程:Activity ---> PhoneWindow ---> DecorView ---> ViewGroup ---> ··· --->View學習
在分析 ViewGroup 的 dispatchTouchEvent 方法以前,先分析一個關鍵的類:TouchTarget。ui
TouchTarget 是 ViewGroup 中的一個靜態內部類。記錄了響應 Touch 事件 View 的鏈表。在 ViewGroup 中,有一個成員變量 private TouchTarget mFirstTouchTarget
,這個變量在 dispatchTouchEvent 分發 Touch 事件中起了重要做用。this
private static final class TouchTarget {
//鏈表的最大長度
private static final int MAX_RECYCLED = 32;
//用於控制同步的鎖鎖
private static final Object sRecycleLock = new Object[0];
//內部用於複用的表頭
private static TouchTarget sRecycleBin;
//可複用的實例鏈表的長度
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// The touched child view.
//記錄響應 Touch 事件的 View
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
// 對目標捕獲的全部指針的指針id的組合位掩碼,和多點觸控有關。
public int pointerIdBits;
// The next target in the target list.
//鏈表中的下一個記錄觸摸 View 的 TouchTarget 對象
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
//應用中,首次被觸摸的時候 sRecycleBin 是爲 null 的。建立一個新對象
target = new TouchTarget();
} else {
//將鏈表的表首的對象賦值爲 target,sRecycleBin 指向下一個對象等待下一次複用。
target = sRecycleBin;
//表頭 sRecycleBin 往下移一位,指向 next 對象。
sRecycleBin = target.next;
//可複用的實例鏈表的長度減一
sRecycledCount--;
//將 target 的 next 賦值爲 null
target.next = null;
}
}
//記錄響應 Touch 事件的 View。
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
//回收 TouchTarget 對象,後面複用
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
//複用鏈表的長度最大不超過 MAX_RECYCLED
if (sRecycledCount < MAX_RECYCLED) {
//在鏈表的最前面加一個可複用的對象做爲表頭。sRecycleBin 指向這個對象。
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
複製代碼
mFirstTouchTarget 是經過 TouchTarget.obtain 方法賦值的(這個後面會說),應用首次被觸摸的時候是經過 new 一個對象來賦值的,後面觸摸的時候,是複用了鏈表首部對象。最後經過 recycle() 方法用來回收本身,用來之後複用。spa
分析以前,咱們先肯定兩個疑問:.net
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//取消並清除觸摸目標,裏面將 mFirstTouchTarget 置爲 null。
// mFirstTouchTarget 是記錄響應觸摸事件 View 的鏈表。
cancelAndClearTouchTargets(ev);
//若是是 DOWN 事件,就重置觸摸狀態。
//將 mGroupFlags 重置 FLAG_DISALLOW_INTERCEPT 標誌位,這個是和 requestDisallowInterceptTouchEvent 相關的,後面再進行解釋。
resetTouchState();
//這兩個方法內部都調用了 clearTouchTargets() 方法,回收 mFirstTouchTarget 指向的鏈表上的對象,將 mFirstTouchTarget 置爲null。
}
①------------
//檢查是否攔截
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//是否不容許攔截
//(requestDisallowInterceptTouchEvent 就是經過給 mGroupFlags 添加FLAG_DISALLOW_INTERCEPT 標誌位來告訴父控件不要攔截的)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//若是容許攔截,那就調用 onInterceptTouchEvent 方法查詢是否攔截,記錄返回值
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//若是不容許攔截,直接將 intercepted 賦值爲 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 事件,而且 mFirstTouchTarget 爲 null,這個 ViewGroup 就繼續攔截事件
//若是可以執行到這裏,說明 DOWN 後面的事件傳遞到了這裏,
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
//是否本身被攔截,或者傳遞的是 CANCEL 事件
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//若是是 DOWN 事件。
//(ACTION_POINTER_DOWN 是一個手指按下的後,另外一個手指也按下傳遞的 DOWN 事件)
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
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;
for (int i = childrenCount - 1; i >= 0; i--) {
②------------
//遍歷子 View
//注意 這裏是倒序遍歷的,先看最上面的 View 是否響應操做事件,其實這也是符合咱們的直覺的.
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 是否在當前點擊區域,若是不在就跳過此次循環
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);
④------------
//這裏調用了 dispatchTransformedTouchEvent 方法,
//這個方法裏面調用子 View 的 dispatchTouchEvent。(後面說明)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//若是 dispatchTransformedTouchEvent 返回了 true,就表明這個 child 消費了這個事件
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();
⑤------------
//調用 addTouchTarget 方法,爲 mFirstTouchTarget 進行賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//沒有找到響應控件的 View,可是 mFirstTouchTarget 不爲null(以前存在響應事件的 View)
//例如:一個手指按在一個按鈕上,另個一手指按在其餘控件上(沒有消費事件)
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
//遍歷鏈表,將 newTouchTarget 指向鏈表的尾部的對象。
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
⑥------------
//若是 mFirstTouchTarget 爲null,沒有子 View 消費事件
//就經過 dispatchTransformedTouchEvent 調用自身的 onTouchEvent.
// No touch targets so treat this as an ordinary view.
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.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//DOWN 事件傳遞過來的時候,是走到這裏的。這一點在前面的流程中有體現。
handled = true;
} else {
// target.child 是否被設置了 CANCEL 事件,或者傳遞過來的是不是 CANCEL 事件。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
⑦------------ //對於非 DOWN 事件,也是在這裏調用 dispatchTransformedTouchEvent 方法進行分發。
/* *若是父控件攔截事件 intercepted 爲true,則 cancelChild 爲 true。 *在調用 dispatchTransformedTouchEvent 進行分發事件的時候,傳遞了一個 CANCEL 事件給子 View(後面說明) */
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
⑧------------
//若是表頭被回收, mFirstTouchTarget 指向下一個對象。
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
//回收對象
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// UP 事件的時候,重置觸摸狀態
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
---------------------------------------------------------------------
//ViewGroup 類
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
//建立一個 TouchTarget 對象
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
//將 mFirstTouchTarget 賦值給 target 的 next。第一次賦值的話,mFirstTouchTarget確定是 null了。
target.next = mFirstTouchTarget ;
//mFirstTouchTarget 賦值爲 target
mFirstTouchTarget = target;
return target;
}
---------------------------------------------------------------------
//ViewGroup 類
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
//遍歷鏈表,找到該 child 所在的 TouchTarget 對象。
if (target.child == child) {
return target;
}
}
return null;
}
---------------------------------------------------------------------
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
//遍歷鏈表,依次調用鏈表上TouchTarget對象的 recycle 方法。
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
//將 mFirstTouchTarget 置爲null。
mFirstTouchTarget = null;
}
}
複製代碼
dispatchTransformedTouchEvent 方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//cancel爲 true,或者傳遞的是一個 CANCEL 事件,設置 cancel 事件
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//若是 child 爲空,就調用父類(View)的 dispatchTouchEvent 方法來分發
//(裏面調用了 onTouchEvent 方法,後面進行分析)
handled = super.dispatchTouchEvent(event);
} else {
//child 不爲 null,就調用子類的分發
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 (newPointerIdBits == 0) {
return false;
}
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);
//將事件分發給子 View
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
複製代碼
問題一:ViewGroup 是怎麼分發給子 View 的?
DOWN 事件: 經過上述源碼分析,ViewGroup 在進行事件分發的時候,先在 DOWN 事件的時候重置一些狀態,而後在 代碼① 處進行詢問是否進行攔截,由於首次傳入的事件是 ACTION_DOWN 事件,而且 mFirstTouchTarget 被重置爲 null,因此就調用 onInterceptTouchEvent 詢問是否攔截。若是 不攔截 就執行到 代碼② ,進行遍歷子 View,尋找在點擊區域的子類(代碼③) ,而後經過(代碼④) dispatchTransformedTouchEvent 方法調用該 child 的 dispatchTouchEvent 方法進行分發。如此事件就分發到了子 View。
此時,若是子 View 消費了 DOWN 事件( child 的 dispatchTouchEvent 返回 true ---> dispatchTransformedTouchEvent 方法返回 true)。就執行到了 代碼⑤ ,經過 addTouchTarget 將該 View 綁定到 TouchTarget 鏈表中,而且是在表頭位置,而且將 newTouchTarget 和 mFristTouchTarget 都賦值指向表頭。若是不消費,就進行下一次循環,尋找消費該事件的子 View。
那麼,若是 ViewGroup 進行事件攔截呢?事件分發該怎麼執行?分爲兩種狀況:1. 直接在 DOWN 事件的時候進行攔截;2. 攔截 MOVE、UP 事件。
MOVE、UP 事件: MOVE、UP 事件可以傳遞過來,那就說明有子 View 消費了 DOWN 事件。mFristTouchTarget 不爲 null。代碼執行到 代碼⑦ ,經過 dispatchTransformedTouchEvent 方法將事件分發給消費了 DOWN 事件的子 View。
延伸知識一:CANCEL 事件產生的內部實現。
CANCEL 事件產生的條件就是,子 View 消費了 DOWN 事件,在 MOVE、UP 事件中,父View 攔截了事件。在 View的事件分發(一)分發流程 中的 ==CANCEL 事件模擬==中看到的日誌是:在第一個 MOVE 事件中,子 View 收到了 CANCEL 事件,第二個 MOVE 事件的時候,ViewGroup 的 onTouchEvent 才接收收到事件。 那麼咱們分析一下流程:
因爲子 View 消費了 DOWN 事件,因此 mFirstTouchTarget 不爲 null 。在第一個 MOVE 事件來臨的時候,代碼執行到了 代碼① ,此時 onInterceptTouchEvent 返回 true,因此 intercepted 被賦值爲 true。而後代碼執行到了 代碼⑦ 。intercepted 爲true, cancelChild 天然也就爲 true。經過 dispatchTransformedTouchEvent 源碼可知,cancel 爲 true 時,就設置了一個 CANCEL 事件給 child。在 代碼⑧ 中,因爲此時是單點觸摸,mFirstTouchTarget 指向的鏈表只有綁定這個 View 的TouchTarget對象,next 爲 null,因此 mFirstTouchTarget 賦值爲 null。如此,child 在父控件攔截的時候就收到了一個 CANCEL 事件。
第二個 MOVE 事件:因爲此時 mFirstTouchTarget 爲null,就直接調用了 代碼⑥ ,經過 dispatchTransformedTouchEvent 調用了自身的 onTouchEvent 方法。
也能夠看出,一旦 ViewGroup 攔截以後, mFirstTouchTarget 重置爲 null,後續事件來的時候直接將 intercepted 設置爲 true,而再也不調用 onInterceptTouchEvent 方法。
問題二:關於 requestDisallowInterceptTouchEvent
requestDisallowInterceptTouchEvent 自己是 ViewParent 中的方法,ViewParent 是一個接口,ViewGroup 實現了它。咱們子類 View 中經過調用getParent().requestDisallowInterceptTouchEvent(true);
來達到請求父控件不攔截。
//ViewGroup 類
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
//給 mGroupFlags 設置標識。
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
複製代碼
在 代碼塊① 中,能夠看到先經過 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
來進行判斷子類 View 是否設置了不容許攔截標識,而後再對自身是否攔截進行判斷。若是不容許攔截將 intercepted 設置爲 false。
在 DOWN 事件中,ViewGroup 會先重置 mGroupFlags 標識。因此並非說子 View 在請求一次不攔截後,父控件就永遠不攔截。
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
//重置 mGroupFlags
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
複製代碼
//View 類
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
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)) {
//若是設置了 mOnTouchListener ,而且 onTouch 返回 true
//就直接 返回結果 true
result = true;
}
//不然,調用 View 的 onTouchEvent 方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
複製代碼
public boolean onTouchEvent(MotionEvent event) {
//------代碼省略-------
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;
//一個不可用的 View 仍然是能夠消費這個事件的,只是沒有響應而已。
//根據 clickable 的值來決定是否消費這個事件。
return clickable;
}
//是否設置了觸摸代理 (若是嘗試擴大 View 的 touch 範圍,可使用觸摸代理)
if (mTouchDelegate != null) {
//若是設置了觸摸代理,就將事件 event 傳遞個觸摸代理的 onTouchEvent 方法來處理。
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//若是 clickable 爲 true ,就執行下面的代碼,並返回 true 消費事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//------代碼省略-------
break;
case MotionEvent.ACTION_DOWN:
//------代碼省略-------
break;
case MotionEvent.ACTION_CANCEL:
//------代碼省略-------
break;
case MotionEvent.ACTION_MOVE:
//------代碼省略-------
break;
}
return true;
}
return false;
}
複製代碼
能夠看出,View 的 dispatchTouchEvent 中,首先進行判斷是否設置了 mOnTouchListener ,若是設置了 mOnTouchListener 而且 mOnTouchListener.onTouch 返回 true,則 View 的 dispatchTouchEvent 方法直接返回 true, 不會調用 View 的 onTouchEvent 方法。不然調用 View 的 onTouchEvent 方法。
在 View 的 onTouchEvent 方法中,咱們能夠看到若是設置了點擊事件和長按事件,那麼 onTouchEvent 必定會返回爲 true了,也就是消費了事件。最終,事件走到這裏就算結束了。
因爲 OnLongClickListener 和 OnClickListener 的回調在 onTouchEvent 方法中調用,因此 當咱們設置了 setOnTouchListener 而且 onTouch 返回 true 的時候,setOnLongClickListener 和 setOnClickListener 是不會被回調的。
View 的 TouchEvent 詳細流程:View的事件分發(三)源碼分析
本篇文章用於記錄學習過程當中的理解和筆記,若有錯誤,請批評指正,萬分感謝!
《Android開發藝術探索》第三章