View的繪制流程二逃片、setContentView

View的繪制流程一中我們已經(jīng)了解了View是怎樣添加到父容器中的屡拨,而在Activity中布局是怎樣被添加進(jìn)去的呢?是不是和View的添加流程一樣呢褥实?帶著疑問我們跟著源碼來看一下布局是怎樣被添加到Activity中的吧
注:現(xiàn)如今的 Android 開發(fā)過程中大多都會繼承 AppCompatActivity 所以本文是以 AppCompatActivity 來講解的

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}
@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    return create(activity, activity.getWindow(), callback);
}
private static AppCompatDelegate create(Context context, Window window,
        AppCompatCallback callback) {
    final int sdk = Build.VERSION.SDK_INT;
    if (BuildCompat.isAtLeastN()) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (sdk >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else if (sdk >= 14) {
        return new AppCompatDelegateImplV14(context, window, callback);
    } else if (sdk >= 11) {
        return new AppCompatDelegateImplV11(context, window, callback);
    } else {
        return new AppCompatDelegateImplV9(context, window, callback);
    }
}

上面一些簡單的代碼追蹤可以看出谷歌工程師為了版本兼容花費了很多心思呀狼。針對不同的版本重寫了各種AppCompatDelegate子類,其中AppCompatDelegateImplN繼承AppCompatDelegateImplV23损离,AppCompatDelegateImplV23繼承AppCompatDelegateImplV14哥艇,AppCompatDelegateImplV14繼承AppCompatDelegateImplV11AppCompatDelegateImplV11繼承AppCompatDelegateImplV9AppCompatDelegateImplV9重寫了setContentView()方法所以getDelegate().setContentView(view);最終會調(diào)用AppCompatDelegateImplV9setContentView()方法

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mOriginalWindowCallback.onContentChanged();
}

上面的代碼看起來很簡單調(diào)用了ensureSubDecor();方法之后獲取一個contentParent的父容器僻澎,移除父容器中所有的子View然后將Activity中設(shè)置的資源文件添加到這個父容器之后通知一下布局發(fā)生改變貌踏。那么mSubDecor是什么呢andorid.R.id.content這個id又是什么呢為什么要將布局添加到contentParent中去呢?帶著疑問我們繼續(xù)來分析ensureSubDecor()中代碼

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        //創(chuàng)建SubDecor
        mSubDecor = createSubDecor();
        // If a title was set before we installed the decor, propagate it now
        CharSequence title = getTitle();
        if (!TextUtils.isEmpty(title)) {
            onTitleChanged(title);
        }
        applyFixedSizeWindow();
        onSubDecorInstalled(mSubDecor);
        mSubDecorInstalled = true;
        // Invalidate if the panel menu hasn't been created before this.
        // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
        // being called in the middle of onCreate or similar.
        // A pending invalidation will typically be resolved before the posted message
        // would run normally in order to satisfy instance state restoration.
        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
        if (!isDestroyed() && (st == null || st.menu == null)) {
            invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
        }
    }
}

這里我們主要分析mSubDecor是怎樣創(chuàng)建的窟勃,跟蹤代碼

/**
 * 該方法主要是進(jìn)行版本兼容使用自己的subDecor來替換phoneWindow中的DecorView
 */
private ViewGroup createSubDecor() {
    //初始化一些style屬性根據(jù)不同的屬性兼容不同的初始布局
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
        throw new IllegalStateException(
                "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
    }
    //設(shè)置狀態(tài)位祖乳。決定初始布局是否需要title、actionBar等等
    //以下都是對window的狀態(tài)進(jìn)行初始化設(shè)置并設(shè)置到feature屬性
    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
        requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
    }
    //判斷window是否是浮窗形式的
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();
    // Now let's make sure that the Window has installed its decor by retrieving it
    //新建phoneWindow下的decorView
    mWindow.getDecorView();
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
    //根據(jù)之前的feature狀態(tài)屬性來初始化subDecor的布局
    ...省略部分代碼...
    //上面省略部分根據(jù)不同條件設(shè)置subDecor的代碼秉氧。這里有個abc+screen_simple我們會分析一下這個布局的代碼
    //其他布局道理也是一樣的
    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
    ...省略部分代碼...
    //下面代碼就很重要了谷歌工程師為了進(jìn)行版本適配在下面代碼進(jìn)行了偷梁換柱
    //獲取contentVIew容器
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);
    //這里的windowContentView是decorView創(chuàng)建的眷昆。
    //上文中setContent方法中出現(xiàn)的android.R.id.content這里又出現(xiàn)了,在上面方法中可以知道activity
    //的資源布局就是添加到id為android.R.id.content的容器中的汁咏。
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // There might be Views already added to the Window's content view so we need to
        // migrate them to our content view
        //這里將windowContentView中所有的子View全部轉(zhuǎn)移到contentView中去
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
        // Change our content FrameLayout to use the android.R.id.content id.
        // Useful for fragments.
        //這里巧妙的將android.R.id.content從windowContentView轉(zhuǎn)移到contentView中實現(xiàn)了subDecor
        //到decorView的替換
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
        // The decorContent may have a foreground drawable set (windowContentOverlay).
        // Remove this as we handle it ourselves
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }
    // Now set the Window's content view with the decor
    //將subDecor添加到windowContentView中
    mWindow.setContentView(subDecor);
    return subDecor;
}

上述代碼是整個setContentView流程的核心亚斋,這里主要就是把phoneWindow中創(chuàng)建的DecorView的內(nèi)容轉(zhuǎn)移到自己的subDecor中去

<android.support.v7.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <android.support.v7.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <android.support.v7.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />
</android.support.v7.widget.FitWindowsLinearLayout>

上面對應(yīng)的是abc_screen_simple的代碼很簡單就不做分析了

@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //創(chuàng)建decorView對象mDecor
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        //這里初始化mDecor布局,并且通過android.R.id.content ID找到mContentParent布局
        //具體初始化流程跟上面的createSubDecor方法類似就不做分析了
        mContentParent = generateLayout(mDecor);
        ...省略部分代碼...
        //剩下代碼主要做一些樣式初始化和設(shè)置就不進(jìn)行分析了
protected DecorView generateDecor(int featureId) {
    // System process doesn't have application context and in that case we need to directly use
    // the context we have. Otherwise we want the application context, so we don't cling to the
    // activity.
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

總結(jié):

到此我們的setContentView流程就已經(jīng)分析結(jié)束了攘滩,從上面的代碼中我們可以分析出AppCompatActivity的布局嵌套為


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帅刊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轰驳,更是在濱河造成了極大的恐慌厚掷,老刑警劉巖弟灼,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冒黑,居然都是意外死亡田绑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門抡爹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掩驱,“玉大人,你說我怎么就攤上這事冬竟∨费ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵泵殴,是天一觀的道長涮帘。 經(jīng)常有香客問我,道長笑诅,這世上最難降的妖魔是什么调缨? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吆你,結(jié)果婚禮上弦叶,老公的妹妹穿的比我還像新娘。我一直安慰自己妇多,他們只是感情好伤哺,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著者祖,像睡著了一般立莉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咸包,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天桃序,我揣著相機(jī)與錄音杖虾,去河邊找鬼烂瘫。 笑死,一個胖子當(dāng)著我的面吹牛奇适,可吹牛的內(nèi)容都是我干的坟比。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嚷往,長吁一口氣:“原來是場噩夢啊……” “哼葛账!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起皮仁,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤籍琳,失蹤者是張志新(化名)和其女友劉穎菲宴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趋急,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡喝峦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了呜达。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谣蠢。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖查近,靈堂內(nèi)的尸體忽然破棺而出眉踱,到底是詐尸還是另有隱情,我是刑警寧澤霜威,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布谈喳,位于F島的核電站,受9級特大地震影響戈泼,放射性物質(zhì)發(fā)生泄漏叁执。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一矮冬、第九天 我趴在偏房一處隱蔽的房頂上張望谈宛。 院中可真熱鬧,春花似錦胎署、人聲如沸吆录。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恢筝。三九已至,卻和暖如春巨坊,著一層夾襖步出監(jiān)牢的瞬間撬槽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工趾撵, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留侄柔,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓占调,卻偏偏與公主長得像暂题,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子究珊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評論 25 707
  • 概述 本篇文章會從源碼(基于Android 6.0)角度分析Android中View的繪制流程薪者,側(cè)重于對整體流程的...
    absfree閱讀 76,897評論 24 273
  • 引言 本章內(nèi)容較多,先養(yǎng)養(yǎng)眼 大家知道剿涮,自定義View有三個重要的步驟:measure言津,layout攻人,draw。而...
    SnowDragonYY閱讀 1,931評論 1 11
  • “蔬菜能生吃就盡量生吃悬槽,這樣可以最大程度保留菜里的營養(yǎng)成分贝椿。”“涼拌菜越自然越好陷谱,能不焯的盡量不焯烙博。”維生素是水溶...
    淺笑等個晴天閱讀 279評論 0 0
  • 何謂君子烟逊?志向恢宏渣窜,品行高潔者也∠芮《周易》有言乔宿,天行健,君子以自強不息访雪。君子師天道之強健详瑞,心懷遠(yuǎn)志,銳意進(jìn)取...
    車前無酒閱讀 377評論 0 1