《Android》事件傳遞過程

1、什麼是事件傳遞?
Android事件傳遞是指用戶操作屏幕產生的一系列動作事件(按下、滑動、擡起)從外層傳遞到的內層的過程。

2、外層到內層如何理解?
Activity —> Window ----> ViewGroup ---->View

3、必須瞭解的基礎
3.1 首先要知道傳遞的對象是一個MotionEvent類的對象。這個類中定義了動作常量,比如“按下” public static final int ACTION_DOWN = 0等等。
3.2 第二要知道3個重要的方法 :
boolean dispatchTouchEvent(MotionEvent ev) 分發事件ev
boolean onInterceptTouchEvent(MotionEvent ev) 是否攔截事件ev
boolean onTouchEvent(MotionEvent ev) 是否消耗事件ev
3.3經常給view設置的有關事件監聽的方法
view.setOnTouchListener(…) 觸摸事件監聽
view.setOnClickListener(…) 單擊事件監聽

4、事件傳遞的具體過程
4.1產生事件:
手指觸碰屏幕首先產生一個MotionEvent Down事件,隨着手指滑動產生多個MotionEvent Move事件,最後擡起手指產生一個MotionEvent Up事件
4.2 事件在Activity中:
Activity中有dispatchTouchEvent()、onTouchEvent()2個方法。

 /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
     /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

dispatchTouchEvent中事件先去傳給getWindow().superDispatchTouchEvent(ev)去做判斷,如果返回爲true,則直接結束。如果爲false 觸發Activity的onTouchEvent()方法。
也就是說在Activity中通過getWindow().superDispatchTouchEvent(ev)方法將事件傳遞給了Window。
4.3 事件在Window中:
getWindow().superDispatchTouchEvent(ev)調用Window內部的superDispatchTouchEvent()方法。

 /**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

這是一個抽象方法。具體邏輯在Window的唯一繼承類PhoneWidow中:

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

事件在window中沒有做任務處理,直接通過mDecor.superDispatchTouchEvent(event)傳遞給了DecorView。DecorView是view的最頂層,被定義在PhoneWindow中的一個對象。它是一個繼承自FrameLayout的ViewGroup。

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

4.4 事件在ViewGroup中:
事件傳遞到DecorView的superDispatchTouchEvent()方法

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

調用了super.dispatchTouchEvent(event),這個super其實就是ViewGroup。
在ViewGroup中有dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()3個方法。
由於源碼過長,其中還包含很多控制單元,爲了分析過程直接貼出來不直觀,不想篇幅過長,自己去查看印象更深刻!直接總結:
(1)事件MotionEvent傳遞給dispatchTouchEvent(ev)進行事件分發
(2)在分發方法內部調用了onInterceptTouchEvent(ev)判讀是否對事件攔截(返回false代表不攔截,true表示攔截。默認爲false)。
(3) onInterceptTouchEvent(ev)返回true, 說明要攔截事件,ev會交給此viewGroup處理。
(4)如果viewGroup設置了OnTouchListener,onTouch()會被調用,onTouch()返回false viewGroup的onTouchEvent會被調用。onTouch()返回true viewGroup的onTouchEvent會被屏蔽掉。
(5)onInterceptTouchEvent(ev)返回false,不攔截事件,將會把事件傳遞給 子View(view或viewGroup)的dispatchTouchEvent(ev)處理。如果子view是ViewGroup則繼續從(1)開始循環。

4.4 事件在View中:
在View中有dispatchTouchEvent()、onTouchEvent()2個方法。沒有onInterceptTouchEvent()
與ViewGroup過程相似,畢竟ViewGroup繼承自View。ViewGroup也沒有重寫onTouchEvent,所以ViewGroup中onTouchEvent()方法就是View中的方法。
(1)事件MotionEvent傳遞給dispatchTouchEvent(ev)進行事件分發
(2)如果view設置了OnTouchListener,onTouch()會被調用,onTouch()返回false view的onTouchEvent會被調用。onTouch()返回true view的onTouchEvent會被屏蔽掉。

5、setOnTouchListener()\setOnClickListener()\onTouchEvent()調用關係:
下面是view的dispatchTouchEvent()源碼

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    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;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            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;
    }

其中判斷了mOnTouchListener != null && mOnTouchListener.onTouch(this, event)條件,之後根據返回結果判斷是否去調用onTouchEvent(event)。驗證了OnTouchListener 調用優先,並控制onTouchEvent()的調用。
在view的onTouchEvent()方法中

 public boolean onTouchEvent(MotionEvent event) {
        ...代碼省略...
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    ...代碼省略...
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                            ...代碼省略...
                        }
         ...代碼省略...

performClickInternal()方法會調performClick(),貼出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);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

這裏觸發了li.mOnClickListener.onClick(this)。說明點擊事件onClick是在onTouchEvent中間接調用的。

6、事件交由上層處理
子視圖不能處理事件時候,onTouchEvent 返回false,事件將交給父視圖的onTouchEvent調用。如果還不能處理事件則依次往上傳遞。直到Activity。也就是在getWindow().superDispatchTouchEvent(ev)返回了false。將調用Activity的onTouchEvent()方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章