事件分發(fā)機(jī)制整理

事件分發(fā)機(jī)制

1、事件分發(fā)、攔截與消費(fèi)

事件分發(fā)用到三個(gè)相關(guān)的方法,dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。
對(duì)于java層,事件是從Activity的dispatchTouchEvent開(kāi)始的,再往上屬于C和C++,這里不做記錄。
以下表格只是展示一下這三個(gè)方法所在的位置,方便后面講解查看。

類型 相關(guān)方法 Activity ViewGroup View
事件分發(fā) dispatchTouchEvent
事件攔截 onInterceptTouchEvent × ×
事件消費(fèi) onTouchEvent ×

2、事件處理

事件處理相對(duì)比較簡(jiǎn)單,所以先說(shuō)一下事件處理邏輯
在activity中為一個(gè)view設(shè)置點(diǎn)擊事件和觸摸事件

view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG,"onClick");
        }
    });
    
view.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.e(TAG,"onTouch"+event.getAction());
            return false;
        }
    });

當(dāng)onTuoch返回false時(shí),onClick方法會(huì)執(zhí)行,返回為true時(shí),onClick不執(zhí)行。
這里只分析事件處理,所以回到View中的dispatchTouchEvent(),以下只貼出關(guān)鍵代碼:

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)) {
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ...
    return result;
}

代碼中可以看到共有四個(gè)判斷條件:

  • li != null
  • li.mOnTouchListener!=null
  • (mViewFlags & ENABLED_MASK) == ENABLED
  • li.mOnTouchListener.onTouch(this, event)

li=mListenerInfo,在activity中view調(diào)用了setOnTouchEvent方法,代碼如下

public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}
ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

由上代碼可以看出mListenerInfo是個(gè)單例,不為null,所以li!=null成立;
li.mOnTouchListener就是在activity中調(diào)用setTouchListener()設(shè)置的OnTouchListener對(duì)象,所以li.mOnTouchListener!=null成立;
(mViewFlags & ENABLED_MASK) == ENABLED,view可以按,所以也成立;
前三個(gè)判斷條件都為true,所以接下來(lái)執(zhí)行mOnTouchListener中的onTouch()方法,所以onTouch決定了result的返回值

    if (!result && onTouchEvent(event)) {
        result = true;
    }

所以,當(dāng)result為false(result默認(rèn)為false),即onTouch返回值為false時(shí),執(zhí)行onTouchEvent()方法,由最開(kāi)始測(cè)試結(jié)果:當(dāng)onTuoch返回false時(shí),onClick方法會(huì)執(zhí)行,返回為true時(shí),onClick不執(zhí)行,猜測(cè)onClick可能會(huì)在onTouchEvent()中執(zhí)行,接下來(lái)看一下onTouchEvent()方法做了些什么,按照慣例還是貼出關(guān)鍵代碼

public boolean onTouchEvent(MotionEvent event) {
    ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ...
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // 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)) {
                            //1
                                performClickInternal();
                            }
                        }
                    }
                ...
                mIgnoreNextUpEvent = false;
                break;
                ...
        }

        return true;
    }

    return false;
}

private boolean performClickInternal() {
    ...
    //2
    return performClick();
}

public boolean performClick() {
    // We still need to call this method to handle the cases where performClick() was called
    // externally, instead of through performClickInternal()
    notifyAutofillManagerOnClick();

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        //3
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}

上面注釋中,

  • //1、onTouchEvent中當(dāng)事件為ACTION_UP時(shí),會(huì)調(diào)用performClickInternal(),
  • //2、然后調(diào)用perfromClick()
  • //3、如果在activity中調(diào)用了setOnClickListener,則li.mOnClickListener不為null,所以最終會(huì)執(zhí)行mOnClickListener的onClick()方法

到這里事件處理執(zhí)行完畢,應(yīng)該明白了為什么當(dāng)onTuoch返回false時(shí),onClick方法會(huì)執(zhí)行,返回為true時(shí),onClick不執(zhí)行。

總結(jié):如果view同時(shí)設(shè)置了OnTouchListener和OnClickListener,當(dāng)onTouch返回為false時(shí),事件的執(zhí)行順序是:OnTouchListener.onTouch ---> View.onTouchEvent ---> View.performClickInternal() ---> View.performClick() --- > OnClickListener.onClick();

2、事件分發(fā)

事件分發(fā)總流程
  1. Activity#dispatchTouchEvent()
  2. PhoneWindow#superDispatchTouchEvent()
  3. DecorView#superDispatchTouchEvent()
  4. ViewGroup#dispatchTouchEvent()
  5. View#dispatchTouchEvent()
  6. View#onTouchEvent()

在Java層面,事件分發(fā)從Activity的dispatchTouchEvent開(kāi)始,再之前數(shù)據(jù)C、C++的范疇,不做深究。
首先看下Activity中的dispatchTouchEvent做了些什么:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

public Window getWindow() {
    return mWindow;
}

Window是一個(gè)抽象類,從Window中的注釋可以看到Window只有一個(gè)實(shí)現(xiàn)類為PhoneWindow,

/ * <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/

從Activity的attach方法也能看出,mWindow就是PhoneDow的實(shí)例:

final void attach(Context context, ActivityThread aThread,
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
}

所以,Activity的dispatchTouchEvent其實(shí)是調(diào)用得PhoneWindow的superDispatchTouchEvent:

public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

PhoneWindow的構(gòu)造函數(shù)中可以知道m(xù)Decor為DecorView的實(shí)例:

public PhoneWindow(Context context, Window preservedWindow,
        ActivityConfigCallback activityConfigCallback) {
    ...
    if (preservedWindow != null) {
        mDecor = (DecorView) preservedWindow.getDecorView();
        ...
}

所以,調(diào)用的是DecorView的superDispatchTouchEvent方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

DecorView是ViewGroup的子類,所以繼續(xù)調(diào)用ViewGroup的dispatchTouchEvent,這里完成了從Activity到ViewGroup的事件分發(fā),ViewGroup的dispatchTouchEvent方法比較復(fù)雜,分段來(lái)看:

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
        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;
        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;
        }

        ...
}

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}
  • 首先判斷事件如果為ACTION_DOWN,則調(diào)用resetTouchState,resetTouchState中又調(diào)用clearTouchTargets,里面對(duì)mFistouchTarget置空。
  • 事件由ACTION_DOWN開(kāi)始,ACTION_UP結(jié)束,上一步中明確了mFirstToucharget = null,所以只有當(dāng)事件開(kāi)始即ACTION_DOWN時(shí),可能會(huì)執(zhí)行方法onInterceptTouchEvent,ACTION_MOVE和ACTION_UP都會(huì)走默認(rèn)執(zhí)行攔截了的方法。
  • 標(biāo)記位FLAG_DISALLOW_INTERCEPT,它一般是由子View的requestDisallowInterceptTouchEvent方法設(shè)置的,表示ViewGroup無(wú)法攔截除了ACTION_DOWN以外的其他動(dòng)作,我們看到源碼第一個(gè)判斷就會(huì)明白,只要是ACTION_DOWN動(dòng)作,這個(gè)標(biāo)記位都會(huì)被重置,并且ViewGroup會(huì)調(diào)用自己onInterceptTouchEvent方法表達(dá)是否需要攔截這新一輪的點(diǎn)擊事件。

接下來(lái)看后續(xù)代碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
                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--) {
                        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 (!child.canReceivePointerEvents()
                                || !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);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                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;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }
        ...
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    ...
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ...
        handled = child.dispatchTouchEvent(transformedEvent);
    }

}

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}
  • isTransformedTouchPointInView點(diǎn)擊位置是否包含在子View,如果不在范圍內(nèi)直接continue,在范圍內(nèi)則調(diào)用dispatchTransformedTouchEvent,里面調(diào)用了child.dispatchTouchEvent,即View的disPatchTouchEvent,這里完成了ViewGroup到View的事件分發(fā)。

  • 如果子View消費(fèi)了事件即dispatchTransformedTouchEvent返回true,則調(diào)用addTouchTarget方法,方法中對(duì)mFirstTouchTarget做了賦值,即mFirstTouchTarget = target。所以當(dāng)ACTION_MOVE和ACTION_UP時(shí),mFirstTouchTarget對(duì)事件攔截起到了關(guān)鍵性作用。

  • 如果子View沒(méi)有消費(fèi)事件即onTouch方法返回false,則mFirstTouchTarget不經(jīng)過(guò)賦值依然為null,
    當(dāng)mFirstTouchTarget == null成立時(shí),執(zhí)行:

      if (mFirstTouchTarget == null) {
          // No touch targets so treat this as an ordinary view.
          handled = dispatchTransformedTouchEvent(ev, canceled, null,
                  TouchTarget.ALL_POINTER_IDS);
      }
                      
      private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
          View child, int desiredPointerIdBits) {
      ...
      if (child == null) {
          handled = super.dispatchTouchEvent(transformedEvent);
      } else {
          ...
          handled = child.dispatchTouchEvent(transformedEvent);
      }
    

    }

可見(jiàn)child為null,調(diào)用super.dispatchTouchEvent,ViewGroup繼承View,所以調(diào)用View的dispatchTouchEvent,完成了ViewGroup到View的事件分發(fā)。
handled即為View的dispatchTouchEvent的返回結(jié)果。

返回Activity中的dispatchTouchEvent,如果handled為false,繼續(xù)執(zhí)行Activity中的onTouchEvent方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容