深入解析Android中的setContentView加载布局原理

前言

对于Android的开发者来说,setContentView大家再熟悉不过了,在我们的Activity中首先就是要用它加载我们的布局,但是应该有一部分人是不知道加载布局的原理,也包括我,今天就从源码的角度分析setContentView加载布局原理。

准备工作

由于我们使用的Android API部分源码是隐藏的,当我们在AndroidStudio中是不能找到源码的,我们可以去官网下载相应源码去查看,当然在GitHub下载相应版本的API替换我们sdk下platforms相应api的android.jar。这样我们就可以在AndroidStudio查看到隐藏的api了,可以断点调试帮助我们阅读源码。

本篇文章分析源码是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最终调用了getWindow()的setContentView·方法,getWindow()返回的是一个Window类,它表示一个窗口的概念,我们的Activity就是一个Window,Dialog和Toast也都是通过Window来展示的,这很好理解,它是一个抽象类,具体的实现是PhoneWindow,加载布局的相关逻辑都几乎都是它处理的。

@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 是否为空,当然第一次启动时mContentParent 时为空的,然后执行installDecor();方法。

mContentParent不为空是通过hasFeature(FEATURE_CONTENT_TRANSITIONS)判断是否有转场动画,当没有的时候就把通过mContentParent.removeAllViews();移除mContentParent节点下的所有View.再通过inflate将我们的把布局填充到mContentParent,最后就是内容变化的回调。至于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 {
 //设置标题
 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,我们看到的布局,标题栏都是它里面。

然后将mDecor作为参数调用generateLayout初始化mContetParent

 protected ViewGroup generateLayout(DecorView decor) {
 // Apply data from current theme.
 //获取主题样式
 TypedArray a = getWindowStyle();
 //......省略样式的设置
 // Inflate the window decor.
 int layoutResource;
 //获取feature并根据其来加载对应的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();
 }

 // 给顶层窗口设置标题和背景
 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获取设置的不同features加载不同的布局,例如我们通常在Activity 加入requestWindowFeature(Window.FEATURE_NO_TITLE);来隐藏标题栏,不管根据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>

通过上面的分析,你应该明白了requestWindowFeature为什么必须在setContentView之前设置了,如果在之后设置,那么通过上面的分析在setContentView执行时已经从本地读取features,而此时还没有设置,当然就无效了。

 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就是在上面判断的根据不同的features,加载的布局,然后将该布局通过addView添加到DecorView.到这里初始都成功了.

 mLayoutInflater.inflate(layoutResID, mContentParent);

在回到最初setContentView中的一句代码,如上,我们也就好理解了,它就是将我们的布局文件inflate到mContentParent中。到这里Activity的加载布局文件就完毕了。

AppCompatActivity的setContentView分析

由于AppCompatActivity的setContentView加载布局的与Activity有很多不同的地方,而且相对Activity稍微复杂点,在这里也简单分析一下。

 @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时一个抽象类,如下图他有几个子类实现

为啥有那么多子类呢,其实通过名字我们也能猜到,是为了兼容。为了证明这点,我们看看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);
 }
 }

这里就很明显了,根据不同的API版本初始化不同的delegate。通过查看代码setContentView方法的实现是在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的加载经验,我们就很容易明白contentParent和Activity中的mContentParent是一个东东,ensureSubDecor就是初始mSubDecor,然后removeAllViews,再将我们的布局填充到contentParent中。最后执行回调。

 private void ensureSubDecor() {
 if (!mSubDecorInstalled) {
  mSubDecor = createSubDecor();
  //省略部分代码
  onSubDecorInstalled(mSubDecor);
 }
 }
 private ViewGroup createSubDecor() {
 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

 //如果哦们不设置置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会调用installDecor,在Activity已经介绍过,主要工作就是初始化mDecor,mContentParent。
 mWindow.getDecorView();
 //省略
//根据设置加载不同的布局
 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设置成android.R.id.content,在此我们应该明白了,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根据主题加载不同的布局subDecor,通过findViewById获取contentView( AppCompat根据不同主题加载的布局中的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中。

//调用两个参数方法
 @Override
 public void setContentView(View view) {
 setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 }
//此处处理和在Activity中分析的setContentView传资源ID进行加载布局是一样的,不同的是此时mContentParent 不为空,先removeAllViews(无转场动画情况)后再直接执行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;
 }

关于subDecor到底是什么布局,我们随便看一个布局R.layout.abc_screen_toolbar,有标题(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工具查看我们的布局层级结构。例如我们AppCompatActivity中setContentView传入的布局文件,是一个线程布局,该布局下有一个Button,则查看到层级结构

参考链接:http://www.weyye.me/detail/framework-appcompatactivity-setcontentview/

总结

以上就是这篇文章的全部内容了,到这里setContentView已经分析完毕,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,由于水平有限,难免有错误,若在阅读时发现不妥或者错误的地方留言指正,谢谢大家对我们的支持。

(0)

相关推荐

  • Android开发中setContentView和inflate的区别分析

    本文实例讲述了Android开发中setContentView和inflate的区别.分享给大家供大家参考,具体如下: 一般用LayoutInflater做一件事:inflate inflate这个方法总共有四种形式(见下面),目的都是把xml表述的layout转化为View对象. 其中有一个比较常用,View inflate(int resource, ViewGroup root),另三个,其实目的和这个差不多. int resource,也就是resource/layout文件在R文件中对

  • 浅析Android Dialog中setContentView()方法

    概述 Dialog在Android中是一个很优秀的工具.在使用Dialog时,我们一般都会自定义要显示的内容布局.Dialog自带了三个方法来支持自定义内容布局. public void setContentView (int layoutResID); public void setContentView (View view); public void setContentView (View view, ViewGroup.LayoutParams params); 这三个方法内部的实现原

  • 深入解析Android中的setContentView加载布局原理

    前言 对于Android的开发者来说,setContentView大家再熟悉不过了,在我们的Activity中首先就是要用它加载我们的布局,但是应该有一部分人是不知道加载布局的原理,也包括我,今天就从源码的角度分析setContentView加载布局原理. 准备工作 由于我们使用的Android API部分源码是隐藏的,当我们在AndroidStudio中是不能找到源码的,我们可以去官网下载相应源码去查看,当然在GitHub下载相应版本的API替换我们sdk下platforms相应api的and

  • Android中替换WebView加载网页失败时的页面

    我们用webView去请求一个网页链接的时候,如果请求网页失败或无网络的情况下,它会返回给我们这样一个页面,如下图所示: 上面这个页面就是系统自带的页面,你觉得是不是很丑?反正小编本人觉得非常丑,很难看,于是乎小编就在想能不能自定义一个页面,当数据请求失败时让系统来加载我们自定义好的页面?上网查了很多资料,都没有关于这个问题的解决方法(反正我是没有找到),经过小编的不断琢磨,今天终于实现了这个功能.以下就是本人自定义实现的数据加载失败时的页面: 这样看起来是不是觉得很高大尚.这和我们真正拿到数据

  • Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而Ima

  • Android中利用动态加载实现手机淘宝的节日特效

    相信去年圣诞节打开过手机淘宝的童鞋都会对当时的特效记忆犹新吧:全屏飘雪,旁边还有个小雪人来控制八音盒背景音乐的播放,让人有种身临其境的感觉,甚至忍不住想狠狠购物了呢(误),大概就是下面这个样子滴: 嗯,确实很炫,那么我们一步步去分析是如何实现的: 一.实现下雪的 View 首先,最上面一层的全屏雪花极有可能是一个顶层的View,而这个View是通过动态加载去控制显示的(不更新淘宝也能看到这个效果).那么我们先得实现雪花效果的 View,人生苦短,拿来就用.打开 gank.io,搜索"雪花&quo

  • Android中的动态加载机制的学习研究

    在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病. Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求.但是有些特殊问题,常常引发我们进一步的沉思.我们从沉思中产生顿悟,从而产生新的技术形式. 如何开发一个可以自定

  • 实例解析JAVA中代码的加载顺序

    Java中代码的加载顺序所能了解的知识点 类的依赖关系 static代码块的加载时间 继承类中构造器的隐式调用 非static的成员变量初始化时间 main方法和static的加载顺序 测试代码如下: public class App { private static App d = new App(); private SubClass t = new SubClass(); static{ System.out.println("App static");//6 } public

  • Android中ListView分页加载数据功能实现

    熟悉Android的朋友们都知道,不管是微博客户端还是新闻客户端,都离不开列表组件,可以说列表组件是Android数据展现方面最重要的组件,我们今天就要讲一讲列表组件ListView加载数据的相关内容.通常来说,一个应用在展现大量数据时,不会将全部的可用数据都呈现给用户,因为这不管对于服务端还是客户端来说都是不小的压力,因此,很多应用都是采用分批次加载的形式来获取用户所需的数据.比如:微博客户端可能会在用户滑动至列表底端时自动加载下一页数据,也可能在底部放置一个"加载更多"按钮,用户点

  • Android中Fragment的加载方式与数据通信详解

    一.加载方式 1. 静态加载 1.1 加载步骤 (1) 创建fragment:创建自定义Fragment类继承自Fragment类,同时将自定义Fragment类与Fragment视图绑定(将layout转换成View) View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) inflater用于绑定Fragment的布局文件,同时将该布局转换成View对象并返回:con

  • 详细讲解Android中使用LoaderManager加载数据的方法

    Android的设计之中,任何耗时的操作都不能放在UI主线程之中.所以类似于网络操作等等耗时的操作都需要使用异步的实现.而在ContentProvider之中,也有可能存在耗时的操作(当查询的数据量很大的时候),这个时候我们也需要使用异步的调用来完成数据的查询. 当使用异步的query的时候,我们就需要使用LoaderManager了.使用LoaderManager就可以在不阻塞UI主线程的情况下完成数据的加载. (1)获取loaderManger:activity.getLoaderManag

  • 在Android中高效的加载大图的方法示例

    将大图加载到内存中总是令人痛苦,因为我们经常会在应用的崩溃报告中看到OOM(Out Of Memory)的bug.大家都知道,Android系统的内存有限.我们必须牢记这一点. stackoverflow上有很多关于大图加载的问题,当你的应用程序遇到OOM的时候,你可以选择直接复制粘贴其中的答案来解决这个问题.因此,你完全可以略过本篇文章,但我想介绍一些加载大图的基础知识及其实际工作的原理. 我只想解释图片解码背后的逻辑.我建议你使用Picasso或Glide来加载图片.没有必要重新发明轮子.

随机推荐