事件分發(二)ViewGroup和View對觸摸事件的分發流程

接着上篇《Activity對觸摸事件的分發流程》,這篇咱們講《ViewGroup和View對觸摸事件的分發流程》。java

一、ViewGroup對觸摸事件的分發流程

ViewGroup.dispatchTouchEvent()

首先是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.onInterceptTouchEvent()

ViewGroup.javaide

/** * 實現此方法來攔截全部觸摸屏動做事件。這容許您在事件發送給孩子時查看它們,並在任什麼時候候得到當前動做的全部權。 * 使用這個方法須要謹慎,由於它與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.buildTouchDispatchChildList()

ViewGroup.java函數

/** * 提供視圖的自定義順序,在其中分配觸摸。這是在緊密循環中調用的,所以不容許分配對象,包括返回數組。相反,您應該返回一個預先分配的列表,該列表將在分派完成後被清除。 * @hide */
public ArrayList<View> buildTouchDispatchChildList() {
    return buildOrderedChildList();
}
複製代碼

ViewGroup.javapost

/** * 填充(並返回)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.dispatchTransformedTouchEvent()

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.addTouchTarget()

ViewGroup.java

/** * 將指定子對象的觸摸目標添加到列表的開頭。假設目標子節點不存在。 */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
複製代碼

二、View對觸摸事件的分發流程

一、先由OnTouchListener的OnTouch()來處理事件,當返回true,則消費該事件,不然由onTouchEvent處理事件; 二、當onTouchEvent返回true時,消費該事件,不然繼續分發該事件。

View.dispatchTouchEvent()

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.onTouchEvent()

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開發者們的專業技能!

Android技術堆棧
Android技術堆棧

本文使用 mdnice 排版

相關文章
相關標籤/搜索