深入解析Android中的setContentView加載布局原理
前言
對于Android的開發(fā)者來說,setContentView大家再熟悉不過了,在我們的Activity中首先就是要用它加載我們的布局,但是應(yīng)該有一部分人是不知道加載布局的原理,也包括我,今天就從源碼的角度分析setContentView加載布局原理。
準(zhǔn)備工作
由于我們使用的Android API部分源碼是隱藏的,當(dāng)我們在AndroidStudio中是不能找到源碼的,我們可以去官網(wǎng)下載相應(yīng)源碼去查看,當(dāng)然在GitHub下載相應(yīng)版本的API替換我們sdk下platforms相應(yīng)api的android.jar。這樣我們就可以在AndroidStudio查看到隱藏的api了,可以斷點調(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()
返回的是一個Window類,它表示一個窗口的概念,我們的Activity就是一個Window,Dialog和Toast也都是通過Window來展示的,這很好理解,它是一個抽象類,具體的實現(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)然第一次啟動時mContentParent 時為空的,然后執(zhí)行installDecor();
方法。
mContentParent不為空是通過hasFeature(FEATURE_CONTENT_TRANSITIONS)
判斷是否有轉(zhuǎn)場動畫,當(dāng)沒有的時候就把通過mContentParent.removeAllViews();
移除mContentParent節(jié)點下的所有View.再通過inflate將我們的把布局填充到mContentParent,最后就是內(nèi)容變化的回調(diào)。至于mContentParent 是什么東東,先留個懸念,稍后再說。
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初始化一個DecorView對象,DecorView繼承了FrameLayout,是我們要顯示布局的頂級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ù)其來加載對應(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; }
代碼較多,先通過getWindowStyle獲取主題樣式進行初始化,然后通過getLocalFeatures獲取設(shè)置的不同features加載不同的布局,例如我們通常在Activity 加入requestWindowFeature(Window.FEATURE_NO_TITLE);
來隱藏標(biāo)題欄,不管根據(jù)Feature最終使用的是哪一種布局,里面都有一個android:id="@android:id/content"
的FrameLayout,我們的布局文件就添加到這個FrameLayout中了。我們看一下一個簡單的布局
<?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>
通過上面的分析,你應(yīng)該明白了requestWindowFeature為什么必須在setContentView之前設(shè)置了,如果在之后設(shè)置,那么通過上面的分析在setContentView執(zhí)行時已經(jīng)從本地讀取features,而此時還沒有設(shè)置,當(dāng)然就無效了。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
通過上面findViewById獲取該對象。不過在獲取ViewGroup之前還有一個重要的方法
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(); }
這個比較好理解,root就是在上面判斷的根據(jù)不同的features,加載的布局,然后將該布局通過addView添加到DecorView.到這里初始都成功了.
mLayoutInflater.inflate(layoutResID, mContentParent);
在回到最初setContentView中的一句代碼,如上,我們也就好理解了,它就是將我們的布局文件inflate到mContentParent中。到這里Activity的加載布局文件就完畢了。
AppCompatActivity的setContentView分析
由于AppCompatActivity的setContentView加載布局的與Activity有很多不同的地方,而且相對Activity稍微復(fù)雜點,在這里也簡單分析一下。
@Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); }
通過名字也就知道把加載布局交給了一個委托對象。
@NonNull public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; }
AppCompatDelegate時一個抽象類,如下圖他有幾個子類實現(xià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。通過查看代碼setContentView方法的實現(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)驗,我們就很容易明白contentParent和Activity中的mContentParent是一個東東,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主題會報錯,就是在這個地方 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會調(diào)用installDecor,在Activity已經(jīng)介紹過,主要工作就是初始化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); //這個就是和我們Activity中的介紹的mDecor層級中的mContentParent是一個東西, 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,我們的布局加載到這個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; }
上面的處理邏輯就是先初始化一些主題樣式,然后通過mWindow.getDecorView()
初始化DecorView.和布局,然后createSubDecor根據(jù)主題加載不同的布局subDecor,通過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。最后通過 mWindow.setContentView(subDecor);
將subDecor添加到DecorView中。
//調(diào)用兩個參數(shù)方法 @Override public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } //此處處理和在Activity中分析的setContentView傳資源ID進行加載布局是一樣的,不同的是此時mContentParent 不為空,先removeAllViews(無轉(zhuǎn)場動畫情況)后再直接執(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到底是什么布局,我們隨便看一個布局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>
不管哪個主題下的布局,都會有一個id 為 abc_screen_content_include最好將id更改為androd.R,content,然后添加到mDecor中的mContentParent中。我們可以同SDK中tools下hierarchyviewer工具查看我們的布局層級結(jié)構(gòu)。例如我們AppCompatActivity中setContentView傳入的布局文件,是一個線程布局,該布局下有一個Button,則查看到層級結(jié)構(gòu)
參考鏈接:http://www.weyye.me/detail/framework-appcompatactivity-setcontentview/
總結(jié)
以上就是這篇文章的全部內(nèi)容了,到這里setContentView已經(jīng)分析完畢,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,由于水平有限,難免有錯誤,若在閱讀時發(fā)現(xiàn)不妥或者錯誤的地方留言指正,謝謝大家對腳本之家的支持。
- 淺析Android Dialog中setContentView()方法
- Android開發(fā)中setContentView和inflate的區(qū)別分析
- Android開發(fā)微信小程序頁面的圖文教程
- Android 登錄頁面的實現(xiàn)代碼(密碼顯示隱藏、EditText 圖標(biāo)切換、限制輸入長度)
- Android Studio使用recyclerview實現(xiàn)展開和折疊功能(在之前的微信頁面基礎(chǔ)之上)
- Android Webview的postUrl與loadUrl加載頁面實例
- Android通過ViewModel保存數(shù)據(jù)實現(xiàn)多頁面的數(shù)據(jù)共享功能
- Android仿微信左右滑動點擊切換頁面和圖標(biāo)
- Android仿淘寶訂單頁面效果
- Android使用setContentView實現(xiàn)頁面的轉(zhuǎn)換效果
相關(guān)文章
Android應(yīng)用框架之應(yīng)用啟動過程詳解
這篇文章主要為大家詳細介紹了Android應(yīng)用框架,應(yīng)用啟動過程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android?Jetpack庫剖析之LiveData組件篇
LiveData是Jetpack組件的一部分,更多的時候是搭配ViewModel來使用,相對于Observable,LiveData的最大優(yōu)勢是其具有生命感知的,換句話說,LiveData可以保證只有在組件( Activity、Fragment、Service)處于活動生命周期狀態(tài)的時候才會更新數(shù)據(jù)2022-07-07Android實現(xiàn)底部半透明彈出框PopUpWindow效果
這篇文章主要為大家詳細介紹了Android實現(xiàn)底部半透明彈出框PopUpWindow效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android輸入框添加emoje表情圖標(biāo)的實現(xiàn)代碼
這篇文章主要為大家詳細介紹了Android輸入框添加emoje表情圖標(biāo)的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android實現(xiàn)聯(lián)動下拉框 下拉列表spinner的實例代碼
這篇文章介紹了Android實現(xiàn)聯(lián)動下拉框 下拉列表spinner的實例代碼,有需要的朋友可以參考一下2013-10-10Android Walker登錄記住密碼頁面功能實現(xiàn)
這篇文章主要為大家詳細介紹了Android Walker登錄記住密碼頁面功能的實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05android的ListView點擊item使item展開的做法的實現(xiàn)代碼
這篇文章主要介紹了android的ListView點擊item使item展開的做法的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12