接着上篇《Activity對觸摸事件的分發流程》,這篇咱們講《ViewGroup和View對觸摸事件的分發流程》。java
首先是Activity中dispatchTouchEvent()方法return super以後將會調用ViewGroup的dispatchTouchEvent()方法:web
一、若是onInterceptTouchEvent()方法返回false,將經過buildTouchDispatchChildList()方法獲取全部能接收該事件的子視圖;然後若是dispatchTransformedTouchEvent()方法返回true,將經過addTouchTarget()生成mFirstTouchTarget對象。數組
二、若是mFirstTouchTarget爲空,說明此時子視圖爲View,將通dispatchTransformedTouchEvent()繼續分發事件;若是mFirstTouchTarget不爲空,說明此時子視圖爲ViewGroup,將遍歷ViewGroup的子視圖經過dispatchTransformedTouchEvent()繼續分發事件。安全
三、dispatchTransformedTouchEvent()方法將經過View的dispatchTouchEvent()方法繼續分發事件。框架
ViewGroup.java
編輯器
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//爲了安全性過濾觸摸事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
// 0xff 動做代碼中動做自己部分的位掩碼。屬於多點觸碰。
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 處理最初的DOWN事件。 if (actionMasked == MotionEvent.ACTION_DOWN) { // 當開始一個新的觸摸手勢時扔掉全部之前的狀態。 // 因爲應用程序切換、ANR或其餘一些狀態變化,框架可能已經放棄了前一個手勢的up或cancel事件。 cancelAndClearTouchTargets(ev); resetTouchState(); } // 檢查攔截。 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //TODO 一、當返回true,表示該事件被當前視圖攔截;當返回false,繼續執行事件分發。 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); //恢復操做,以防它被更改 } else { intercepted = false; } } else { // 沒有觸摸目標,這個動做不是初始向下,因此這個ViewGroup繼續攔截觸摸。 intercepted = true; } // 若是被攔截,啓動正常的事件分發。另外,若是已經有一個處理手勢的視圖,則執行正常的事件分派。 if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // 檢查取消。 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // 若是須要,更新指針指向的觸摸目標列表。 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 若是事件的目標是可訪問性焦點,那麼咱們將它交給具備可訪問性焦點的視圖,若是它不處理它,咱們將清除標誌並像往常同樣將事件分派給全部的孩子。 // 咱們正在查找可訪問性集中的主機,以免保持狀態,由於這些事件很是罕見。 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; 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; //清理這個指針id的早期觸摸目標,以防它們不一樣步。 removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //TODO 二、從視圖最上層到下層,獲取全部能接收該事件的子視圖 final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); 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 (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 孩子已經在其範圍內接受觸摸。 // 除了它正在處理的指針以外,再給它一個新的指針。 newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // TODO 三、分發觸摸事件 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 孩子想在本身的範圍內接受觸摸。 mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex點成預分類列表,查找原始索引 for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // TODO 四、添加觸摸目標 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // 可訪問性焦點沒有處理事件,所以清除標記並對全部孩子進行正常分派。 ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // 沒有找到一個孩子來接收這個事件。 // 將指針分配給最近添加的最少的目標。 newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 分發給觸摸目標。 if (mFirstTouchTarget == null) { // TODO 三、分發觸摸事件。沒有接觸目標,因此把這看成一個普通的View。 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 發送給觸摸目標,不包括新的觸摸目標,若是咱們已經發送給它。必要時取消接觸目標。 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //TODO 三、分發觸摸事件 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; } } // 更新列表的觸摸目標指針或取消,若是須要。 if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } return handled; } 複製代碼
ViewGroup.java
ide
/** * 實現此方法來攔截全部觸摸屏動做事件。這容許您在事件發送給孩子時查看它們,並在任什麼時候候得到當前動做的全部權。 * 使用這個方法須要謹慎,由於它與View.onTouchEvent(MotionEvent)的交互至關複雜,使用它須要以正確的方式實現這兩個方法。事件將按如下順序接收: * 一、您將在這裏接收down事件。 * 二、down事件將由這個視圖組的一個子視圖組處理,或者交給您本身的onTouchEvent()方法來處理;這意味着您應該實現onTouchEvent()來返回true,這樣您將繼續看到手勢的其他部分(而不是尋找父視圖來處理它)。另外,經過從onTouchEvent()返回true,您將不會在onInterceptTouchEvent()中收到任何後續事件,而且全部的觸摸處理都必須在onTouchEvent()中正常發生。 * 三、只要從這個函數返回false,接下來的每一個事件(直到幷包括最終的up)都將首先在這裏傳遞,而後傳遞到目標的onTouchEvent()。 * 四、若是您從這裏返回true,您將不會接收到任何如下事件:目標視圖將接收到相同的事件,可是帶有action MotionEvent#ACTION_CANCEL,而且全部進一步的事件將被傳遞到您的onTouchEvent()方法,而且再也不出如今這裏。 * * @param ev 要沿層次結構向下分派的觸摸事件。 * @return 返回true從子元素中竊取動做事件,並經過onTouchEvent()將它們分配給這個ViewGroup。當前目標將接收一個ACTION_CANCEL事件,這裏再也不傳遞任何消息。 */
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;
}
複製代碼
ViewGroup.java
函數
/** * 提供視圖的自定義順序,在其中分配觸摸。這是在緊密循環中調用的,所以不容許分配對象,包括返回數組。相反,您應該返回一個預先分配的列表,該列表將在分派完成後被清除。 * @hide */
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
複製代碼
ViewGroup.java
post
/** * 填充(並返回)mPreSortedChildren一個視圖的子元素的預約列表,首先按Z排序,而後按子元素繪製順序排序(若是適用的話)。此列表在使用後必須清除,以免泄漏子視圖。使用一個穩定的插入排序,一般是O(n)的viewgroup不多上升的孩子。 */
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) { mPreSortedChildren = new ArrayList<>(childrenCount); } else { // callers should clear, so clear shouldn't be necessary, but for safety... mPreSortedChildren.clear(); mPreSortedChildren.ensureCapacity(childrenCount); } final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = 0; i < childrenCount; i++) { // 將下一個子元素(按子元素順序)添加到列表的末尾 final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View nextChild = mChildren[childIndex]; final float currentZ = nextChild.getZ(); // 在全部Z值較大的視圖以前插入 int insertIndex = i; while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { insertIndex--; } mPreSortedChildren.add(insertIndex, nextChild); } return mPreSortedChildren; } 複製代碼
ViewGroup.java
學習
/** * 將觸摸事件轉換爲特定子視圖的座標空間,篩選不相關的指針id,並在必要時覆蓋其操做。若是child爲空,則假設MotionEvent將被髮送到這個ViewGroup。 */
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) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // 計算要傳遞的指針的數量。 final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // 若是因爲某種緣由,咱們最終處於不一致的狀態,看起來咱們可能會產生一個沒有指針的觸摸事件,那麼就刪除該事件。 if (newPointerIdBits == 0) { return false; } // 若是指針的數量是相同的,而且咱們不須要執行任何複雜的不可逆轉換,那麼咱們能夠爲這個分派重用motion事件,只要咱們當心地還原咱們所作的任何更改。不然咱們須要複印一份。 final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { //TODO * 分發觸摸事件 handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); //TODO * 分發觸摸事件 handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // 執行任何須要的轉換和分派。 if (child == null) { //TODO * 分發觸摸事件 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()); } //TODO * 分發觸摸事件 handled = child.dispatchTouchEvent(transformedEvent); } // 結束 transformedEvent.recycle(); return handled; } 複製代碼
ViewGroup.java
/** * 將指定子對象的觸摸目標添加到列表的開頭。假設目標子節點不存在。 */
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
複製代碼
一、先由OnTouchListener的OnTouch()來處理事件,當返回true,則消費該事件,不然由onTouchEvent處理事件; 二、當onTouchEvent返回true時,消費該事件,不然繼續分發該事件。
View.java
/** * 將觸摸屏觸摸事件向下傳遞到目標視圖,若是是目標視圖,則傳遞到該視圖。 * @param event 要分發的觸摸事件。 * @return 若是事件是由視圖處理的,則爲真,不然爲假。 */
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在Down事件以前,若是存在滾動操做則中止。不存在則不進行操做
stopNestedScroll();
}
// mOnTouchListener.onTouch優先於onTouchEvent。 if (onFilterTouchEventForSecurity(event)) { //當存在OnTouchListener,且視圖狀態爲ENABLED時,調用onTouch()方法 ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; //若是已經消費事件,則返回True } //若是OnTouch()沒有消費Touch事件則調用OnTouchEvent() if (!result && onTouchEvent(event)) { // [見小節2.5.1] result = true; //若是已經消費事件,則返回True } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // 處理取消或擡起操做 if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; } 複製代碼
View.java
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
// 當View狀態爲DISABLED,若是可點擊或可長按,則返回True,即消費事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //當View狀態爲ENABLED,若是可點擊或可長按,則返回True,即消費事件; //與前面的的結合,可得出結論:只要view是可點擊或可長按,則消費該事件. 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) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { setPressed(true, x, y); } if (!mHasPerformedLongPress) { //這是Tap操做,移除長按回調方法 removeLongPressCallback(); if (!focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } //調用View.OnClickListener if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } //獲取是否處於可滾動的視圖內 boolean isInScrollingContainer = isInScrollingContainer(); if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); //當處於可滾動視圖內,則延遲TAP_TIMEOUT,再反饋按壓狀態,用來判斷用戶是否想要滾動。默認延時爲100ms postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { //當再也不滾動視圖內,則馬上反饋按壓狀態 setPressed(true, x, y); checkForLongClick(0); //檢測是不是長按 } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); if (!pointInView(x, y, mTouchSlop)) { removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; } 複製代碼
方法 | 返回 | 解釋 |
---|---|---|
Activity.dispatchTouchEvent() | true | 消費事件 |
Activity.dispatchTouchEvent() | false | 消費事件 |
Activity.dispatchTouchEvent() | super | 向下分發事件給ViewGroup.dispatchTouchEvent(),若是是DOWN事件,則觸發onUserInteraction() |
ViewGroup.dispatchTouchEvent() | true | 消費事件 |
ViewGroup.dispatchTouchEvent() | false | 向上分發事件給Activity.onTouchEvent() |
ViewGroup.dispatchTouchEvent() | super | 分發事件給ViewGroup.onInterceptTouchEvent() |
Activity.onTouchEvent() | true | 消費事件 |
Activity.onTouchEvent() | false | 消費事件 |
Activity.onTouchEvent() | super | 消費事件 |
ViewGroup.onInterceptTouchEvent() | true | 分發事件給ViewGroup.OnTouchListener.onTouch() |
ViewGroup.onInterceptTouchEvent() | false | 向下分發事件給View.dispatchTouchEvent() |
ViewGroup.onInterceptTouchEvent() | super | 向下分發事件給View.dispatchTouchEvent() |
ViewGroup.OnTouchListener.onTouch() | true | 消費事件 |
ViewGroup.OnTouchListener.onTouch() | false | 分發事件給ViewGroup.onTouchEvent() |
ViewGroup.onTouchEvent() | true | 消費事件 |
ViewGroup.onTouchEvent() | false | 向上分發事件給Activity.onTouchEvent() |
ViewGroup.onTouchEvent() | super | 向上分發事件給Activity.onTouchEvent() |
View.dispatchTouchEvent() | true | 消費事件 |
View.dispatchTouchEvent() | false | 向下分發事件給View.OnTouchListener.onTouch() |
View.dispatchTouchEvent() | super | 向下分發事件給View.OnTouchListener.onTouch() |
View.OnTouchListener.onTouch() | true | 消費事件 |
View.OnTouchListener.onTouch() | false | 分發事件給View.onTouchEvent() |
View.onTouchEvent() | true | 消費事件 |
View.onTouchEvent() | false | 向上分發事件給ViewGroup.OnTouchListener.onTouch() |
View.onTouchEvent() | super | 消費事件 |
一、onInterceptTouchEvent返回值true表示事件攔截, onTouch/onTouchEvent 返回值true表示事件消費。
二、觸摸事件先交由Activity.dispatchTouchEvent。再一層層往下分發,當中間的ViewGroup都不攔截時,進入最底層的View後,開始由最底層的OnTouchEvent來處理,若是一直不消費,則最後返回到Activity.OnTouchEvent。
三、ViewGroup纔有onInterceptTouchEvent攔截方法。在分發過程當中,中間任何一層ViewGroup均可以直接攔截,則再也不往下分發,而是交由發生攔截操做的ViewGroup的OnTouchEvent來處理。
四、子View可調用requestDisallowInterceptTouchEvent方法,來設置disallowIntercept=true,從而阻止父ViewGroup的onInterceptTouchEvent攔截操做。
五、OnTouchEvent由下往上冒泡時,當中間任何一層的OnTouchEvent消費該事件,則再也不往上傳遞,表示事件已處理。
六、若是View沒有消費ACTION_DOWN事件,則以後的ACTION_MOVE等事件都不會再接收。
七、只要View.onTouchEvent是可點擊或可長按,則消費該事件.
八、onTouch優先於onTouchEvent執行,onTouch的位置在onTouchEvent前面。當onTouch返回true,則不執行onTouchEvent,不然會執行onTouchEvent。onTouch只有View設置了OnTouchListener,且是enable的才執行該方法。
歡迎關注Android技術堆棧,專一於Android技術學習的公衆號,致力於提升Android開發者們的專業技能!
本文使用 mdnice 排版