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

 更新时间:2017年09月11日 11:15:52   作者:Code4Android  
在日常开发Android中setContentView是必不可少的一部分,下面这篇文章主要给大家介绍了关于Android中setContentView的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习下吧。

GPT4.0+Midjourney绘画+国内大模型 会员永久免费使用!
如果你想靠AI翻身,你先需要一个靠谱的工具!

前言

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

准备工作

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

本篇文章分析源码是Android7.1(API25)。

Activiy setContentView源码分析

1
2
3
4
5
6
7
8
/**
 * 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,加载布局的相关逻辑都几乎都是它处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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 是什么东东,先留个悬念,稍后再说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 ,以及一些属性的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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中了。我们看一下一个简单的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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,而此时还没有设置,当然就无效了。

1
2
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

通过上面findViewById获取该对象。不过在获取ViewGroup之前还有一个重要的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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.到这里初始都成功了.

1
mLayoutInflater.inflate(layoutResID, mContentParent);

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


AppCompatActivity的setContentView分析

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

1
2
3
4
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}

通过名字也就知道把加载布局交给了一个委托对象。

1
2
3
4
5
6
7
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}

AppCompatDelegate时一个抽象类,如下图他有几个子类实现

为啥有那么多子类呢,其实通过名字我们也能猜到,是为了兼容。为了证明这点,我们看看create方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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中

1
2
3
4
5
6
7
8
@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中。最后执行回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//调用两个参数方法
 @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)的情况加载的布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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已经分析完毕,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,由于水平有限,难免有错误,若在阅读时发现不妥或者错误的地方留言指正,谢谢大家对脚本之家的支持。

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:http://www.jianshu.com/p/6f68043f5613

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

最新评论