Android UI绘制分析(一)-解析布局资源,完成布局
本文源码基于 Android sdk 26, 为了逻辑清晰,省略了无关代码,不排除后期重新加上相关代码
当用户在activity中 调用setContentView 方法时,将自己的布局id 设置到activity中。而activity通过内部持有的window对象 来设置。
/**
* 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.
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()方法 返回 内部持有的window对象,其具体实例是PhoneWindow。
mWindow = new PhoneWindow(this, window, activityConfigCallback);
phoneWindow 实现setContentView操作
显然,我们需要看看phoneWindow中setContentView做了什么。
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
mContentParent.requestApplyInsets();
}
mContentParent 实际上就是一个ViewGroup, setContentView方法实际上就把设置的布局资源解析成View后,再添加到 mContentParent中,完成整个布局。so,这个mContentParent从何而来,
1. 根据Activity主题配置,创建DecorView
phoneWindow的setContentView 方法中,首先调用installDecor(),初始化DecorView , 和mContentParent。
private void installDecor() {
if (mDecor == null) {
//创建 DecorView
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
//解析mContetnParent
mContentParent = generateLayout(mDecor);
}
}
而DecorView 本质上就是一个FrameLayout。
public class DecorView extends FrameLayout
我们先看如何初始化DecorView。
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
}
嗯,很简单。 我们再看如何初始化 mContentView
protected ViewGroup generateLayout(DecorView decor) {
int layoutResource;
int features = getLocalFeatures();
//根据activity主题中设置的参数 获取不同的系统内置的布局资源id
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
//将获取到布局资源id 添加到mDecor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//系统内置全部布局资源中 都会一个id为ID_ANDROID_CONTENT 的 ViewGroup,获取到并赋值给contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
return contentParent;
}
2. 解析用户设置的view,并添加到DecorView中
在上面方法中,第一步,会根据当前activity的主题信息 加载不同的布局资源,并添加到DecorView中。
第二步,从布局资源中获取 id为 ID_ANDROID_CONTENT 的view 并赋值给contentParent 返回,这个view便是上文中 mContentParent,在之后,会将用户自己的布局 添加到mContentParent中,从而达到界面显示的目的。
/**
*DecorView 直接解析根据主题配置 获取到的系统布局id,并添加到自己身上
*/
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
所以最终一个Activity界面显示的布局应该是这样的:
activity界面显示布局视图.png总结
所以一个View是如何被添加到屏幕窗口上的呢。
- 创建顶层布局容器DecorView
- 在顶层布局中加载基础布局ViewGroup
- 将ContentView添加到基础布局中的FrameLayout中