想要学好自定义View, 就要知道Android是如何将View显示到屏幕上的, 我们都知道我们创建Activity,设置setContentView()后, 我们的布局就显示在了我们的屏幕上, 今天我们就来看看setContentView()到底做了什么神奇的事情。
在开始之前, 我们要带着疑问去看源码。
疑问:
- setContentView()到底做了什么, 为什么调用后就可以先出我们想要的布局页面?
- PhoneWindow是什么东西?Window和它是什么关系?
- DecorView是干什么用的?和我们的布局有什么关系?
- 为什么requesetFeature()要在setContentView()之前调用?
相信大家在做开发的时候都会遇到这些疑问。
Activity的setContentView
注: 我们先讲activity中的setContentView()
我们现在就点进setConentView()一探究竟。我们点进去一看
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
它调用的是getWindow()的setContentView(), 那这个Window是什么呢。我们可以看Window类的注释。这里解释下注释:Window就是显示View的顶层的窗口, 并包含一些行为的封装。 这个Window的实例要作为一个顶级View添加到WindowManager中, 它提供一些背景, 标题栏, 默认的key事件的一些处理。
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
...
}
Window的实现类只有一个PhoneWindow, 因此, 刚才的getWindow().setContentView(layoutResID)
其实调用的是PhoneWindow的setContentView()。好,我们现在去PhoneWindow的setContentView()看看。
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.
//首先他会对mContentParent判空
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是什么呢? 它是一个ViewGroup, 也就是它是一个容器,那么通过它的方法名, 我们也可以猜到, 它是一个现实内容的容器, 也就是我们的Activity要显示的内容,就会包到这个容器中。我们可以看这个mContentParent的注释。 它说这个mContentParent可以是DeocrView或者是它的子类。
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
在PhoneWindow类中我们再看DecorView的注释。说这是window的顶层view,包含了window的装饰。那这个DecorView是什么呢, 我们点这个类一看它就是一个FrameLayout.
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
至此, 我们大体上可以知道我们的Activity显示的是什么了。首先最外面是一个PhoneWindow, PhoneWindow里面包含了一个DecorView。
好, 我们继续往下看PhoneWindow的setContentView()。我们会看到这么一句话
mLayoutInflater.inflate(layoutResID, mContentParent);
上面的代码把我们的XML布局添加到了mContentParent的容器中。我们暂且先这样猜测一下。
那我们DecorView是如何初始化的呢?那我们要看它的installDecor()方法了。我们点击去看。代码有点长, 我先贴上来。
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);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
// Only inflate or create a new TransitionManager if the caller hasn't
// already set a custom one.
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
if (mTransitionManager == null) {
final int transitionRes = getWindowStyle().getResourceId(
R.styleable.Window_windowContentTransitionManager,
0);
if (transitionRes != 0) {
final TransitionInflater inflater = TransitionInflater.from(getContext());
mTransitionManager = inflater.inflateTransitionManager(transitionRes,
mContentParent);
} else {
mTransitionManager = new TransitionManager();
}
}
mEnterTransition = getTransition(mEnterTransition, null,
R.styleable.Window_windowEnterTransition);
mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReturnTransition);
mExitTransition = getTransition(mExitTransition, null,
R.styleable.Window_windowExitTransition);
mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReenterTransition);
mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
R.styleable.Window_windowSharedElementEnterTransition);
mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReturnTransition);
mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
R.styleable.Window_windowSharedElementExitTransition);
mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReenterTransition);
if (mAllowEnterTransitionOverlap == null) {
mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowEnterTransitionOverlap, true);
}
if (mAllowReturnTransitionOverlap == null) {
mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowReturnTransitionOverlap, true);
}
if (mBackgroundFadeDurationMillis < 0) {
mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
R.styleable.Window_windowTransitionBackgroundFadeDuration,
DEFAULT_BACKGROUND_FADE_DURATION_MS);
}
if (mSharedElementsUseOverlay == null) {
mSharedElementsUseOverlay = getWindowStyle().getBoolean(
R.styleable.Window_windowSharedElementsUseOverlay, true);
}
}
}
}
开始解释: 首先当Decor为空的时候它先会generateDecor(-1)生成一个Decor, 点进去看一下。这个方法就是new 了一个DeocrView ,代码我就不贴上来了。我们继续往下看,它把Decor生成之后, 它会把它丢到generateLayout(mDecor)方法中, 并且返回值赋给我们的ContentParent。 在这个地方, 我们来看一下generateLayout()方法里面做了些什么事情。这个方法非常的长,我就不贴上来了,我们挑重点的说, 其他的自行查看源码。
首先, 它会去拿我们的WindowStyle, 我们在XML设置的一些window相关style属性, 就是在这里加进来的。我们往后看, 比如, 它会判断window是不是floating的, 像Dialog这类的就是floating的。就是浮着的类型。我们在往后走, 会有一个我们熟悉的windowNoTitle的style, 它会调用requestFeature()方法。还有好多判断,大家可以自行去看。
看到这里我们开头的一个问题到这里已经渐渐明朗了, 为什么requesetFeature()要在setContentView()之前调用? 因为在我们setContentView()的时候, 就要根据不同的feature, 去加载不同的布局文件。这个布局文件, 就是将要加载到我们的Decor里面的。代码好多 就不贴上来了。
// Inflate the window decor.
int layoutResource;
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) {
...
}
我们就挑个简单的布局(R.layout.screen_simple)看一下。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<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:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
其中id为content的就是要显示我们真正布局的地方, 我们来验证下。继续看源码,它会调用
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
这个就是加载我们的布局, 同时findViewById,找到了我们刚才看的framelayout, 我们最后可以发现它返回的就是这个这个framelayout.
return contentParent;
那么讲到这里, 也就是说我们的mContentParent它最终代表的就是我们的DecorView布局里面id为content的FrameLayout.而我们刚才说了在PhoneWindow的setContentView()中mContentParent会最终加载我们的布局。也就是说, 我们Activity的布局最终显示在id为content的FrameLayout下面的。
如果有同学看不明白, 我这里放一张图, 能更加清晰的理解。
1_Activity加载UI-类图关系和视图结构.png而我们开头的有些疑惑都可以解开了:
PhoneWindow是什么东西?Window和它是什么关系? 我们每个Activity有一个这样的window, 这个window就是用来显示我们的UI的。
DecorView是干什么用的?和我们的布局有什么关系? DecorView是我们Window顶层的一个view, 我们自己的布局是加载到DecorView中的id为content的FrameLayout中的。
讲到这里, 我们仅仅只是把DecorView的视图结构给分析了一把, 那么我们自己的布局到底是怎么从我们的XML生成并且添加到DecorView上面的呢?因此,我们就要来看下LayoutInflater, 它是怎么把XML添加到DecorView中的?
当然我们继续要带着疑问去看源码。
相信我们平时在写我们的布局的时候, 会用到我们的include标签, 会用到我们的merge标签。你们会不会有这样的疑问:
- include标签为什么不能作为我们的XML的根节点吗?
- merge标签为什么它要作为XML资源布局的根节点?
接下来我们就带着疑问去看一下源码。其实我们所有的疑惑都可以在源码中找到答案。所以在开发过程中,不能有一知半解, 一定要花时间去探究一下。
我们的PhoneWindow的setContentView()源码并没有分析完, 因此我们继续看, 这时候就要看它的inflate方法了。
mLayoutInflater.inflate(layoutResID, mContentParent);
刚才我们猜测过, 它这里肯定是把我们XML解析成view添加到mContentParent中去, 我们点进去看一下,它调用的是mLayoutInflater.inflate(layoutResID, mContentParent,true)。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
首先它会去哪一个XML的解析器, 这个不细说了不是重点,我们继续到下一个inflate方法去,
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
首先,回去拿attrs, xml中设置的一些属性, 往下走, 它会把我们这支的contentParent赋值给result, 它会去找根节点, 它会去判断是不是merge标签, 如果root为空, 或者attachToRoot我false, 就会抛异常,这也就是为什么merge要作为顶层标签。接着他会调用rInflate(parser, root, inflaterContext, attrs, false);
这个放着先待会再讲。
当是一些常用的标签的时候, 它首先会final View temp = createViewFromTag(root, name, inflaterContext, attrs);
去生成常用布局,假设我们这个标签是LinearLayout, 接着会调用params = root.generateLayoutParams(attrs);
也就是说, 我们XML中的那些layout属性,都是调用对应的父节点的root.generateLayoutParams去解析属性,生成layoutParams, 添加上去的。
当把我们的根节点解析完成后, 会去解析子节点
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
这个方法还是调用了void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate)
方法。这个方法在是merge和不是merage的时候都会调用, 只是最后的一个参数传的不同, 为merge标签的时候,最后参数为false, 其他情况为true.这个方法内部也是遍历循环解析.在这个方法里面我们就能回答刚才提出的两个问题了。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
到这里如果还没有明白的同学, 可以结合下图,可以更加容易的理解整个流程。
LayoutInflater.png讲到这里我们小结一下:
每个Activity都有一个关联的Window对象, 用来描述应用程序窗口, 每一个窗口内部又包含一个DecorView对象, DecorView对象用来描述窗口的视图--xml布局。
AppCompatActivity的setContentView
上述是在activity下创建DecorView的过程。
接下来我们要加个餐。我们用AS开发并不是继承Activity, 而是继承AppCompatActivity。所以他的setContentView()是谷歌工程做过优化的, 我们点进去看一下。
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
一看名字, 就知道这是个代理。它这个代理就是代理了一些专门用来做兼容的一些类。getDelegate()
获取一个AppCompatDelegate类, 这是一个抽象类, 它有好多实现, 这个自己点进去看, 很容易看明白。
那我们这个setContentView()是在哪个类中实现的呢? 不卖关子了, 是AppCompatDelegateImplV9中实现的。我们就去这个类中看看。我们到这个类的setContentView()中去看
@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();
}
我们看第一行的方法。ensureSubDecor()
里面显示调用了createSubDecor()
这个里面会很熟悉。我们点进去看。这个方法和PhoneWindow的generateLayout()
很像,你们发现了吗? 这个方法里, 它会去先获取一些AppCompatTheme属性,这里就能解释为什么我们在style里面不去使用AppCompatTheme的主题的话,程序就会闪退了。因为我们默认继承的是AppCompatActivity。它的setContentView()内部获取的主题是AppCompatTheme的。我们继续往下看,我们会看到这么一行代码。
// Now let's make sure that the Window has installed its decor by retrieving it
mWindow.getDecorView();
这个类还是会去初始化decorView的, 我们点进去看它还是调用了installDecor()
去生成DecorView。
我们上面讲过PhoneWindow的generateLayout()
里面是根据不同的style去加载不同的布局, 这个createSubDecor()
也不例外, 我们看它后面的代码, 也是根据获取的style去加载不同的布局。
我们挑一个简单的subDecor看看,R.layout.abc_screen_simple。
<android.support.v7.widget.FitWindowsLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/action_bar_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:fitsSystemWindows="true">
+
+ <android.support.v7.widget.ViewStubCompat
+ android:id="@+id/action_mode_bar_stub"
+ android:inflatedId="@+id/action_mode_bar"
+ android:layout="@layout/abc_action_mode_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/abc_screen_content_include" />
+
+</android.support.v7.widget.FitWindowsLinearLayout>
里面的include就是一个framelayout
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
所以它整个结构就是一个linearlayout里面一个viewstub和一个framelayout。为什么要这个搞? 我们接着看,等下你就明白了。这就是一个subDecor。
我们继续看代码。首先会把id为action_bar_activity_content的这个Framelayout找到, 然后找到android.R.id.content, 然后谷歌工程师它把这个content的id抹掉, 赋值到这个Framelayout上去了。然后把这个subDecor设置到window的DecorView中的FrameLayout中去了。
谷歌工程师为了做兼容真的是花了很多心思。看到这里应该都明白了吧,谷歌工程师有套了一层decorView进去,并把content的id名重新赋值给新添加的subDecor中的FrameLayout作为我们要设置内容的布局。
谷歌这么做是为了不用改变我们自己的编程习惯,以前是叫content,现在还是叫content, 对开发者来说比较方便。
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
...
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
如果还没有理解的同学,我再放上一张图用来更好的理解一下兼容的setContentView吧。
AppCompatActivity的View布局结构.png大家也可以自己用AS去自己看看view层级结构。
好了, 关于setContentView也已经讲述完毕了,下一节我们讲解DecorView是如何添加到Window上的,敬请期待。
参考
课后笔记
本文为猫舍原创作品,转载请注明原文出处: http://imlerry.com/2017/05/08/02.Android%E9%AB%98%E7%BA%A7UI%E7%B3%BB%E5%88%97%EF%BC%881%EF%BC%89-setContentView-%E8%AF%A6%E8%A7%A3/。
网友评论