在activity里面如果想加载布局并显示的话,可以用setContentView来设置;如果想动态添加控件,可以用addView来添加(其实setContentView方法内部也通过addView方法来实现的),那么View是如何被放在视图上面并且显示的呢,这篇博客我们来了解一下布局的加载过程。
setContentView()有三个方法,三个方法的除了参数不同其他调用都是一样的,我们以最常用的看setContentView(layoutResID)为例:
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
setContentView()首先调用getWindow()方法返回一个Window,然后我们去看Window类,发现Window类是一个抽象类,setContentView()方法也是一个抽象方法,而Window的具体实现类是PhoneWindow,所以调用的setContentView()方法实际上也就是PhoneWindow的setContentView()方法。
但是在源码中按住Ctrl却发现无法进入到PhoneWindow类里面,这是因为安卓把这个类给隐藏掉了。想要查看的话可以去SDK的安装目录中找到\sources\
文件夹下面搜索查看,我们以android-24为例:
这样就能找到PhoneWindow类并查看源码了。
我们继续往下看PhoneWindow的setContentView()方法:
@Override
public void setContentView(int layoutResID) {
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();
}
}
setContentView()主要步骤为两步:
当第一次加载的时候mContentParent为null,会去调用installDecor()方法,installDecor()方法里面又是什么呢?
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
}
......
}
首先判断mDecor为null,调用generateDecor()获取mDecor,
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
我们看到这里返回的是一个DecorView,这是PhoneWindow类当中的一个内部类,同时也是屏幕显示的内容最外层。下面同样判断mContentParent如果为null,就会执行generateLayout()方法,并且把我们创建好的DecorView布局当作参数传进去;
/**
*generateLayout方法的作用就是设置一些窗体的属性值,然后窗体布局添加到DecorView中,并返回窗体布局中ID为content的帧布局
*/
protected ViewGroup generateLayout(DecorView decor) {
//获取窗口的属性
TypedArray a = getWindowStyle();
//... (设置窗口style属性)
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
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();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
mDecor.finishChanging();
return contentParent;
}
这个方法比较长,我们总结一下这个方法都做了什么:
1. 获取窗口的各种style属性,设置title是否显示,窗口是否浮动等
2. 根据获取到的feature值来选择不同的窗口布局文件(窗口修饰类型包括有全屏FullScreen,不含标题栏NoTitleBar等)以screen_simple.xml
为例。
<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>
3、上面的
填充到DecorView里面的,
即为标题栏,
即我们的布局区域。 DecorView会通过addView的方式将上面的布局添加进去,并通过findViewById将content控件绑定到contentParent(contentParent实际上是一个FrameLayout),最后作为返回值提供给方法调用处为mContentView赋值。
@Override
public void setContentView(int layoutResID) {
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();
}
}
返回来再看PhoneWindow中的setContentView(int layoutResID)方法。
LayoutInflater.inflate(layoutResID, mContentParent)会将传入的布局填充到mContentParent中(将我们自己写的Layout布局添加到帧布局中),这样即可以将我们的布局加载到Activity中了。
本篇我们总结了布局的加载过程,如有错误,恳请大家批评指正。在下篇文章中我们会继续探索UI的绘制流程。