深入解析Android中的setContentView加載布局原理
前言
對(duì)于Android的開(kāi)發(fā)者來(lái)說(shuō),setContentView大家再熟悉不過(guò)了,在我們的Activity中首先就是要用它加載我們的布局,但是應(yīng)該有一部分人是不知道加載布局的原理,也包括我,今天就從源碼的角度分析setContentView加載布局原理。
準(zhǔn)備工作
由于我們使用的Android API部分源碼是隱藏的,當(dāng)我們?cè)贏ndroidStudio中是不能找到源碼的,我們可以去官網(wǎng)下載相應(yīng)源碼去查看,當(dāng)然在GitHub下載相應(yīng)版本的API替換我們sdk下platforms相應(yīng)api的android.jar。這樣我們就可以在AndroidStudio查看到隱藏的api了,可以斷點(diǎn)調(diào)試幫助我們閱讀源碼。
本篇文章分析源碼是Android7.1(API25)。
Activiy setContentView源碼分析
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
在Activity中setContentView最終調(diào)用了getWindow()的setContentView·方法,getWindow()返回的是一個(gè)Window類,它表示一個(gè)窗口的概念,我們的Activity就是一個(gè)Window,Dialog和Toast也都是通過(guò)Window來(lái)展示的,這很好理解,它是一個(gè)抽象類,具體的實(shí)現(xiàn)是PhoneWindow,加載布局的相關(guān)邏輯都幾乎都是它處理的。
@Override
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
先判斷mContentParent 是否為空,當(dāng)然第一次啟動(dòng)時(shí)mContentParent 時(shí)為空的,然后執(zhí)行installDecor();方法。
mContentParent不為空是通過(guò)hasFeature(FEATURE_CONTENT_TRANSITIONS)判斷是否有轉(zhuǎn)場(chǎng)動(dòng)畫(huà),當(dāng)沒(méi)有的時(shí)候就把通過(guò)mContentParent.removeAllViews();移除mContentParent節(jié)點(diǎn)下的所有View.再通過(guò)inflate將我們的把布局填充到mContentParent,最后就是內(nèi)容變化的回調(diào)。至于mContentParent 是什么東東,先留個(gè)懸念,稍后再說(shuō)。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
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) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
(mIconRes != 0 && !mDecorContentParent.hasIcon())) {
mDecorContentParent.setIcon(mIconRes);
} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
mIconRes == 0 && !mDecorContentParent.hasIcon()) {
mDecorContentParent.setIcon(
getContext().getPackageManager().getDefaultActivityIcon());
mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
}
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
mDecorContentParent.setLogo(mLogoRes);
}
// 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) && !mIsStartingWindow) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
//設(shè)置標(biāo)題
mTitleView = (TextView) findViewById(R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
//......初始化屬性變量
}
}
在上面的方法中主要工作就是初始化mDecor和mContentParent ,以及一些屬性的初始化
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());
}
generateDecor初始化一個(gè)DecorView對(duì)象,DecorView繼承了FrameLayout,是我們要顯示布局的頂級(jí)View,我們看到的布局,標(biāo)題欄都是它里面。
然后將mDecor作為參數(shù)調(diào)用generateLayout初始化mContetParent
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//獲取主題樣式
TypedArray a = getWindowStyle();
//......省略樣式的設(shè)置
// Inflate the window decor.
int layoutResource;
//獲取feature并根據(jù)其來(lái)加載對(duì)應(yīng)的xml布局文件
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// 給頂層窗口設(shè)置標(biāo)題和背景
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
代碼較多,先通過(guò)getWindowStyle獲取主題樣式進(jìn)行初始化,然后通過(guò)getLocalFeatures獲取設(shè)置的不同features加載不同的布局,例如我們通常在Activity 加入requestWindowFeature(Window.FEATURE_NO_TITLE);來(lái)隱藏標(biāo)題欄,不管根據(jù)Feature最終使用的是哪一種布局,里面都有一個(gè)android:id="@android:id/content"的FrameLayout,我們的布局文件就添加到這個(gè)FrameLayout中了。我們看一下一個(gè)簡(jiǎn)單的布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <!-- Popout bar for action modes --> <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:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle"> <TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
通過(guò)上面的分析,你應(yīng)該明白了requestWindowFeature為什么必須在setContentView之前設(shè)置了,如果在之后設(shè)置,那么通過(guò)上面的分析在setContentView執(zhí)行時(shí)已經(jīng)從本地讀取features,而此時(shí)還沒(méi)有設(shè)置,當(dāng)然就無(wú)效了。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
通過(guò)上面findViewById獲取該對(duì)象。不過(guò)在獲取ViewGroup之前還有一個(gè)重要的方法
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
這個(gè)比較好理解,root就是在上面判斷的根據(jù)不同的features,加載的布局,然后將該布局通過(guò)addView添加到DecorView.到這里初始都成功了.
mLayoutInflater.inflate(layoutResID, mContentParent);
在回到最初setContentView中的一句代碼,如上,我們也就好理解了,它就是將我們的布局文件inflate到mContentParent中。到這里Activity的加載布局文件就完畢了。

AppCompatActivity的setContentView分析
由于AppCompatActivity的setContentView加載布局的與Activity有很多不同的地方,而且相對(duì)Activity稍微復(fù)雜點(diǎn),在這里也簡(jiǎn)單分析一下。
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
通過(guò)名字也就知道把加載布局交給了一個(gè)委托對(duì)象。
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
AppCompatDelegate時(shí)一個(gè)抽象類,如下圖他有幾個(gè)子類實(shí)現(xiàn)

為啥有那么多子類呢,其實(shí)通過(guò)名字我們也能猜到,是為了兼容。為了證明這點(diǎn),我們看看create方法
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);
}
}
這里就很明顯了,根據(jù)不同的API版本初始化不同的delegate。通過(guò)查看代碼setContentView方法的實(shí)現(xiàn)是在AppCompatDelegateImplV9中
@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();
}
有了分析Activity的加載經(jīng)驗(yàn),我們就很容易明白contentParent和Activity中的mContentParent是一個(gè)東東,ensureSubDecor就是初始mSubDecor,然后removeAllViews,再將我們的布局填充到contentParent中。最后執(zhí)行回調(diào)。
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
//省略部分代碼
onSubDecorInstalled(mSubDecor);
}
}
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//如果哦們不設(shè)置置AppCompat主題會(huì)報(bào)錯(cuò),就是在這個(gè)地方
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.");
}
//省略..... 初始化一下屬性
ViewGroup subDecor = null;
//PhtoWindowgetDecorView會(huì)調(diào)用installDecor,在Activity已經(jīng)介紹過(guò),主要工作就是初始化mDecor,mContentParent。
mWindow.getDecorView();
//省略
//根據(jù)設(shè)置加載不同的布局
if (!mWindowNoTitle) {
if (mIsFloating) {
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
// Floating windows can never have an action bar, reset the flags
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
/**
* This needs some explanation. As we can not use the android:theme attribute
* pre-L, we emulate it by manually creating a LayoutInflater using a
* ContextThemeWrapper pointing to actionBarTheme.
*/
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
Context themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
} else {
themedContext = mContext;
}
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
/**
* Propagate features to DecorContentParent
*/
if (mOverlayActionBar) {
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (mFeatureProgress) {
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
}
if (mFeatureIndeterminateProgress) {
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
}
}
} else {
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
if (Build.VERSION.SDK_INT >= 21) {
// If we're running on L or above, we can rely on ViewCompat's
// setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(subDecor,
new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
final int top = insets.getSystemWindowInsetTop();
final int newTop = updateStatusGuard(top);
if (top != newTop) {
insets = insets.replaceSystemWindowInsets(
insets.getSystemWindowInsetLeft(),
newTop,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}
// Now apply the insets on our view
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
} else {
// Else, we need to use our own FitWindowsViewGroup handling
((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
new FitWindowsViewGroup.OnFitSystemWindowsListener() {
@Override
public void onFitSystemWindows(Rect insets) {
insets.top = updateStatusGuard(insets.top);
}
});
}
}
if (subDecor == null) {
throw new IllegalArgumentException(
"AppCompat does not support the current theme features: { "
+ "windowActionBar: " + mHasActionBar
+ ", windowActionBarOverlay: "+ mOverlayActionBar
+ ", android:windowIsFloating: " + mIsFloating
+ ", windowActionModeOverlay: " + mOverlayActionMode
+ ", windowNoTitle: " + mWindowNoTitle
+ " }");
}
if (mDecorContentParent == null) {
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
//contentView 是我們布局填充的地方
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//這個(gè)就是和我們Activity中的介紹的mDecor層級(jí)中的mContentParent是一個(gè)東西,
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
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.
//清除windowContentView的id
windowContentView.setId(View.NO_ID);
//將contentView的id設(shè)置成android.R.id.content,在此我們應(yīng)該明白了,contentView 就成為了Activity中的mContentParent,我們的布局加載到這個(gè)view中。
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 填充到DecorView中
mWindow.setContentView(subDecor);
//省略部分代碼
return subDecor;
}
上面的處理邏輯就是先初始化一些主題樣式,然后通過(guò)mWindow.getDecorView()初始化DecorView.和布局,然后createSubDecor根據(jù)主題加載不同的布局subDecor,通過(guò)findViewById獲取contentView( AppCompat根據(jù)不同主題加載的布局中的View R.id.action_bar_activity_content)和windowContentView (
DecorView中的View android.R.id.content)控件。獲取控件后將windowContentView 的id清空,并將 contentView的id由R.id.action_bar_activity_content更改為android.R.id.content。最后通過(guò) mWindow.setContentView(subDecor);將subDecor添加到DecorView中。
//調(diào)用兩個(gè)參數(shù)方法
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//此處處理和在Activity中分析的setContentView傳資源ID進(jìn)行加載布局是一樣的,不同的是此時(shí)mContentParent 不為空,先removeAllViews(無(wú)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)情況)后再直接執(zhí)行mContentParent.addView(view, params);即將subDecor添加到mContentParent
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
關(guān)于subDecor到底是什么布局,我們隨便看一個(gè)布局R.layout.abc_screen_toolbar,有標(biāo)題(mWindowNoTitle為false)并且有ActionBar(mHasActionBar 為true)的情況加載的布局。
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.ActionBarOverlayLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/decor_content_parent" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <include layout="@layout/abc_screen_content_include"/> <android.support.v7.widget.ActionBarContainer android:id="@+id/action_bar_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" style="?attr/actionBarStyle" android:touchscreenBlocksFocus="true" android:gravity="top"> <android.support.v7.widget.Toolbar android:id="@+id/action_bar" android:layout_width="match_parent" android:layout_height="wrap_content" app:navigationContentDescription="@string/abc_action_bar_up_description" style="?attr/toolbarStyle"/> <android.support.v7.widget.ActionBarContextView android:id="@+id/action_context_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" android:theme="?attr/actionBarTheme" style="?attr/actionModeStyle"/> </android.support.v7.widget.ActionBarContainer> </android.support.v7.widget.ActionBarOverlayLayout>
不管哪個(gè)主題下的布局,都會(huì)有一個(gè)id 為 abc_screen_content_include最好將id更改為androd.R,content,然后添加到mDecor中的mContentParent中。我們可以同SDK中tools下hierarchyviewer工具查看我們的布局層級(jí)結(jié)構(gòu)。例如我們AppCompatActivity中setContentView傳入的布局文件,是一個(gè)線程布局,該布局下有一個(gè)Button,則查看到層級(jí)結(jié)構(gòu)

參考鏈接:http://www.weyye.me/detail/framework-appcompatactivity-setcontentview/
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,到這里setContentView已經(jīng)分析完畢,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,由于水平有限,難免有錯(cuò)誤,若在閱讀時(shí)發(fā)現(xiàn)不妥或者錯(cuò)誤的地方留言指正,謝謝大家對(duì)腳本之家的支持。
- 淺析Android Dialog中setContentView()方法
- Android開(kāi)發(fā)中setContentView和inflate的區(qū)別分析
- Android開(kāi)發(fā)微信小程序頁(yè)面的圖文教程
- Android 登錄頁(yè)面的實(shí)現(xiàn)代碼(密碼顯示隱藏、EditText 圖標(biāo)切換、限制輸入長(zhǎng)度)
- Android Studio使用recyclerview實(shí)現(xiàn)展開(kāi)和折疊功能(在之前的微信頁(yè)面基礎(chǔ)之上)
- Android Webview的postUrl與loadUrl加載頁(yè)面實(shí)例
- Android通過(guò)ViewModel保存數(shù)據(jù)實(shí)現(xiàn)多頁(yè)面的數(shù)據(jù)共享功能
- Android仿微信左右滑動(dòng)點(diǎn)擊切換頁(yè)面和圖標(biāo)
- Android仿淘寶訂單頁(yè)面效果
- Android使用setContentView實(shí)現(xiàn)頁(yè)面的轉(zhuǎn)換效果
相關(guān)文章
Android應(yīng)用框架之應(yīng)用啟動(dòng)過(guò)程詳解
這篇文章主要為大家詳細(xì)介紹了Android應(yīng)用框架,應(yīng)用啟動(dòng)過(guò)程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android?Jetpack庫(kù)剖析之LiveData組件篇
LiveData是Jetpack組件的一部分,更多的時(shí)候是搭配ViewModel來(lái)使用,相對(duì)于Observable,LiveData的最大優(yōu)勢(shì)是其具有生命感知的,換句話說(shuō),LiveData可以保證只有在組件( Activity、Fragment、Service)處于活動(dòng)生命周期狀態(tài)的時(shí)候才會(huì)更新數(shù)據(jù)2022-07-07
Android實(shí)現(xiàn)底部半透明彈出框PopUpWindow效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)底部半透明彈出框PopUpWindow效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android輸入框添加emoje表情圖標(biāo)的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android輸入框添加emoje表情圖標(biāo)的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android實(shí)現(xiàn)聯(lián)動(dòng)下拉框 下拉列表spinner的實(shí)例代碼
這篇文章介紹了Android實(shí)現(xiàn)聯(lián)動(dòng)下拉框 下拉列表spinner的實(shí)例代碼,有需要的朋友可以參考一下2013-10-10
Android Walker登錄記住密碼頁(yè)面功能實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android Walker登錄記住密碼頁(yè)面功能的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
android的ListView點(diǎn)擊item使item展開(kāi)的做法的實(shí)現(xiàn)代碼
這篇文章主要介紹了android的ListView點(diǎn)擊item使item展開(kāi)的做法的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12

