圖解Android View的事件傳遞

轉(zhuǎn)載請標(biāo)注: http://www.itdecent.cn/p/bea1bb4aac95

一、UI overview

在說 View的事件傳遞過程之前先看下UI overview。

圖1 UI overview

這里的 screen_simple.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

Activity并不負(fù)責(zé)視圖控制,它只是控制生命周期和事件處理,真正控制視圖的是Window。一個Activity包含一個Window,Window才是真正代表一個窗口,Window 中持有一個 DecorView,而這個DecorView才是 view 的根布局。

打個不恰當(dāng)比喻吧,Window類相當(dāng)于一幅畫(抽象概念,什么畫我們未知) ,PhoneWindow為一副齊白石先生的山水畫(具體概念,我們知道了是誰的、什么性質(zhì)的畫),DecorView則為該山水畫的具體內(nèi)容(有山、有水、有樹,各種界面)。DecorView呈現(xiàn)在PhoneWindow上。

上面兩段文字摘自網(wǎng)絡(luò)

DecorView繼承FrameLayout, 即它本是一個ViewGroup. PhoneWindow通過 installDecor()來安裝DecorView. 如圖所示,

圖2 window and decorview

installDecor() 主要做了以下幾件事情

  • 生成DecorView的實(shí)例 (這樣PhoneWindow就可以有View根的引用了)
  • Android為DecorView做了一些通用的Layout(比如帶標(biāo)題、ActionBar等等)
    如:
    FEATURE_SWIPE_TO_DISMISS -> screen_swipe_dismiss.xml
    FEATURE_ACTION_MODE_OVERLAY -> screen_simple_overlay_action_mode.xml
    DEFAULE -> screen_simple.xml
    這里的Layout會根據(jù)Window的Feature去做具體的選擇,也就是開發(fā)者自己選擇。但它們都有一個共同的特點(diǎn),就是所有的Layout里都包含有android:id/content這個子ViewGroup, 而這個content就是給Activity里 setContentView所使用的。
  • PhoneWindow引用到App需要定制的mContentParent.
    mContentParent指向通用Layout里的 android:id/content, 而這個content 即是Activity里setContentView里的Parent View.

下面來看下setContentView關(guān)鍵代碼, 可見它將開發(fā)者自定義的layout inflate到了mContentParent里了

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
    //將Activity里的layout inflate到 mContentParent里
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

二、Activity/ViewRootImpl以及WMS之間的關(guān)系

可以參考下這篇文章,http://www.itdecent.cn/p/c223b993b1ec

圖3 Activity ViewRootImpl Wms之間的關(guān)系

上圖發(fā)生的順序如下:

  1. 初始化Activity
    ActivityThread在實(shí)例化一個Activity后,會將AMS 傳遞過來的信息attach到Activity里,同時生成一個PhoneWindow對象.
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window) {
    attachBaseContext(context);

    mWindow = new PhoneWindow(this, window);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
…
}
  1. 接著為PhoneWindow安裝DecorView (一般是在 Activity.onCreate()調(diào)用setContentView來最終調(diào)用到installDecor)
  2. WindowManager 將ViewRootImpl.W傳給 WMS管理, (具體是 addView )
    a) 生成 ViewRootImpl
    b) 將DecorView作為 根view
    c) 生成 WindowInputEventReceiver 用于接收觸摸按鍵等事件(具體的注冊動作是在WMS里進(jìn)行的)
    d) 通過Session.addToDisplay 將W 傳給WMS

從上面3點(diǎn)可知:

  1. 一個App只有一個WindowManager (WindowManagerGlobal.getInstance())
  2. WindowManagerGlobal管理著App里所有的RootView(DecorView或addView里的根View), ViewRootImpl,以及對應(yīng)的LayoutParams
  3. 每個Activity僅有一個DecorView
  4. 每個Activity僅有一個PhoneWindow
  5. ViewRootImpl的個數(shù)與addView的個數(shù)相關(guān), 并不一定只有一個。

ViewRootImpl.W
該類用于WindowManagerService通知一些UI相關(guān)事件發(fā)生了,如:
dispatchAppVisiblity
windowFocusChanged
activateWindow

三、View的事件流程

圖3 的WindowInputEventReceiver類用于接收IMS傳遞過來的按鍵、觸屏等相關(guān)事件。下面以觸屏事件為例來看下事件傳遞過程

圖4 View事件傳遞

上圖灰色背景一般是APP不能直接處理的,而淺綠色背景的模塊則是APP需要定制的。

3.1 View都不處理事件

圖5 View都不處理事件

上圖表示所有的View都沒有處理 ACTION_DOWN事件,那么就沒有必要把后面的事件(ACTION_MOVE/ACTION_UP)再往下發(fā)送了。
這樣后面來的ACTION_MOVE和ACTION_UP都將不會處理了。
具體的實(shí)現(xiàn)是在ViewGroup.dispatchTouchEvent, 通過判斷mFirstTouchTarget是否為空來決定是否還繼續(xù)往子view發(fā)送。 因?yàn)槿绻凶觱iew consume了ACTION_DOWN事件的話,那么 mFirstTouchTarget將不會為NULL.

3.2 子View處理事件

圖6 子view處理事件

View里因?yàn)樵O(shè)置了 onClickListener(), 這樣就導(dǎo)致 View是 clickable (或者可以直接在xml里加上android:clickable=”true”),即可點(diǎn)擊,那么View.onTouchEvent就會永遠(yuǎn)返回 True, 代表View consume了該事件。
注意:只要View consume了該事件,那么該事件既不會往下傳(不會傳給子view),也不會往上傳(后面Activity/ViewGroup 的 onTouchEvent將不會再調(diào)用)。

3.3 ViewGroup攔截

圖7 ViewGroup攔截事件

onInterceptTouchEvent 是攔截的意思,它是攔截事件不往下傳(不會再傳給子View),但是會將事件往上傳(相關(guān)的 Activity/ViewGroup 會調(diào)用 onTouchEvent).
上圖 onInterceptTouchEvent攔截了,所以不會再將ACTION_DOWN往View里傳了,直接返回到 onTouchEvent. 因?yàn)锳CTION_DOWN沒有被任何View consume, 所以也沒必要繼續(xù)發(fā)送 ACTION_MOVE/ACTION_UP事件了。

3.4 ViewGroup consume事件

圖8 ViewGroup consume事件

上圖是ViewGroup的 onTouchEvent consume了事件,那么說明是ViewGroup需要處理事件,所以將后續(xù)的 ACTION_MOVE/ACTION_UP直接發(fā)送給ViewGroup處理了,就不再需要往子View傳遞事件了。

四、總結(jié)

onTouchEvent 返回值表示是否 consume該事件,它的傳遞方向是往上傳遞,View -> ViewGroup->Activity. 但是一旦某個View或ViewGroup的onTouchEvent返回True, 就不會再往上傳遞了。

onInterceptTouchEvent 是ViewGroup特有的,表示是否攔截事件往下(子View)傳遞,Activity->ViewGroup->View, 如果ViewGroup攔截了事件,那么會直接調(diào)用ViewGroup 的onTouchEvent。

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

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

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