1. 程式人生 > >Android6.0原始碼解讀之Activity點選事件分發機制

Android6.0原始碼解讀之Activity點選事件分發機制

    本篇博文是Android點選事件分發機制系列博文的第四篇,主要是從解讀Activity類的原始碼入手,根據原始碼理清Activity點選事件分發原理,並掌握Activity點選事件分法機制。特別宣告的是,本原始碼解讀是基於最新的Android6.0版本。

    Android中通常點選事件用MotionEvent來表示,當一個點選操作發生時,事件最先傳遞給當前的Activity,由Activity的dispatchTouchEvent來進行事件的分發,具體工作是由Activity內部的Window來完成的。Window會將事件傳遞給decor view,decor view一般就是當前介面的底層容器(即setContentView所設定的View的父容器),通過Activity.getWindow.getDecorView()可以獲得。關於Activity事件分發機制的原始碼,我們重點來看下dispatchTouchEvent方法。

(一)dispatchTouchEvent原始碼解析

    /**
     * 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(); } //一開始事件交給Activity所附屬的Window進行派發,如果返回true,整個事件迴圈就結束了,返回false意味著事件沒人處理,所有人的onTouchEvent都返回了false,那麼Activity就要來做最後的收場。
if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }

onUserInteraction方法

     /**
     * Called as part of the activity lifecycle when an activity is about to go
     * into the background as the result of user choice.  For example, when the
     * user presses the Home key, {@link #onUserLeaveHint} will be called, but
     * when an incoming phone call causes the in-call Activity to be automatically
     * brought to the foreground, {@link #onUserLeaveHint} will not be called on
     * the activity being interrupted.  In cases when it is invoked, this method
     * is called right before the activity's {@link #onPause} callback.
     *
     * <p>This callback and {@link #onUserInteraction} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * @see #onUserInteraction()
     */
    protected void onUserLeaveHint() {
    }

(二)onTouchEvent原始碼解析

    通常一個點選操作要是沒有被Activity下的任何View處理,則Activity的onTouchEvent將會被呼叫。返回值為true表明你已經消費了這個事件,false則表示沒有消費(預設)。

    /**
     * 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) {
    // mWindow物件其實就是dispatchTouchEvent方法裡的getWindow()物件
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

事件傳遞給ViewGroup

    接下來我們看Window是如何將事件傳遞給ViewGroup的。通過原始碼我們知道Window是一個抽象類,而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);

    既然superDispatchTouchEvent是個抽象方法,那麼我們去看下它的實現類PhoneWindow是如何處理點選事件的。

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

    通過以上原始碼不難發現,PhoneWindow將事件直接傳遞給了Decor View。我們來看下這裡的DecorView。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

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

  @Override
  public final View getDecorView() {
    if (mDecor == null) {
      installDecor();
    }
    return mDecor;
  }

    通常我們通過((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)這種方式獲取Activity所設定的View。這個mDecor顯然就是getWindow().getDecorView()返回的View,而我們通過setContentView設定的View是它的一個子View。目前事件傳遞到了DecorView 這裡,由於DecorView 繼承自FrameLayout且是我們的父View,所以最終事件會傳遞給我們的View。從這裡開始,事件已經傳遞到我們的頂級View了,所謂的頂級View實際上是最底層View,也叫根View。

參考:《Android開發的藝術》