Android——UI(一):UI绘制流程
UI绘制流程
1、为什么调用setContentView之后就可以显示我们想要的布局?
进入setContentView方法可以看到
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
其中getWindow()返回了一个Window对象,Window是一个抽象类,Window中有很多抽象方法,最常见的就是我们经常写的findViewById。
通过注释
/**
* 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.
*
*The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
可以看到Window处于最顶层,并且在Android中只有PhoneWindow一个子类。
进入PhoneWindow的setContentView可以看到
@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)) {
//Activity的转场动画
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,通过注释
// 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.
可以看到mContentParent 就是Window的内容,注释中的mDecor是DecorView,Android中最顶层的View。
往下看会看到mLayoutInflater.inflate(layoutResID, mContentParent);
将id设置到ViewGroup中,这里暂不做深入。
当mContentParent为空时会执行installDecor();
进去之后,找到
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
继续查看generateLayout(mDecor)
TypedArray a = getWindowStyle();
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
这里代码是不是很熟悉,在这里就会获取到Window的style。
继续往下看
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;
}
在这里通过不同的features去加载不同的layout,同时也说明了,requestFeature必须在setContentView之前进行调用。
再来看layoutResource即将加载的布局是什么,在这里以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>
小结:
- 每个Activity都有一个关联的Window抽象类,用来描述应用程序窗口。
- 在Android中,Window有且仅有一个实现类PhoneWindow
- PhoneWindow中包含了一个DecorView
- 我们自己设置的布局在DecorView下的FrameLayout中
- 最后通过mLayoutInflater.inflate将id与mContentParent进行关联
2、LayoutInflater如何把xml转成View?
- 为什么include不能作为xml资源文件布局根节点?
- 为什么merge只能作为xml资源文件布局根节点?
带着疑问继续看源码。
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) + ")");
}
//获取xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
//使用解析器获取View
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
进入inflate(parser, root, attachToRoot)
继续看,
//获取xml中的属性集
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
//获取根节点
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
final String name = parser.getName();
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 {
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);
}
// 获取xml上的属性,在自定义属性时经常用到
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 设置属性
temp.setLayoutParams(params);
}
}
// 解析完父容器之后继续解析子控件
rInflateChildren(parser, temp, attrs, true);
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
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) {
//include不能作为跟标签
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//merge必须作为跟标签
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();
}
}