Android setContentView与LayoutInflater加载解析机制源码分析
- Activity 和 Window之间的关系
- Activity 内有三个 setContentView重载的方法
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
- Activity 内维护了一个Window --
Window 是一个抽象类 有个实现类 PhoneWindow PhoneWindow 内部维护了一个DecorWindow
Window是一个抽象类,提供了绘制窗口的一组通用API。
PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。
DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View
- 窗口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.
if (mContentParent == null) {
//下面分析
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//应用程序里可以多次调用setContentView()来显示界面,因为会removeAllViews。
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
-->
private void installDecor() {
if (mDecor == null) {
//首先判断mDecor对象是否为空,如果为空则调用generateDecor()
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor);
//......
//初始化一堆属性值
}
}
-->
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
generateDecor()创建一个DecorView(该类是 FrameLayout子类,即一个ViewGroup视图);
-->
//generateDecor执行完之后会执行generateLayout
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//......
//依据主题style设置一堆值进行设置
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
//......
//根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值
//把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
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");
}
//......
//继续一堆属性设置,完事返回contentParent
return contentParent;
}
根据窗口的风格修饰类型为该窗口选择不同的窗口根布局文件。mDecor做为根视图将该窗口根布局添加进去,然后获取id为content的FrameLayout返回给mContentParent对象。所以installDecor方法实质就是产生mDecor和mContentParent对象。
我们平时requestWindowFeature()设置的值就是在这里通过getLocalFeature()获取的;而android:theme属性也是通过这里的getWindowStyle()获取的。
- 总结过程
- 创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
*依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。
- 将Activity的布局文件添加至id为content的FrameLayout内。
- 至此整个setContentView的主要流程就分析完毕。
View二三
- LayoutInflator
可以将一个布局达成一个View对象,该类会调用一系列方法,底层使用Pull解析 解析出每个View的TAg 调用createViewFromTag(),该方法又会调用createView 方法 通过反射的方式 创建出View的对象并返回。 - LayoutInflator 打出来的布局 为什么宽高属性会失效
首先View必须存在于一个布局中,之后如果将layout_width 设置成 match_parent表示让View的宽度填充满布局,如果 设置成 wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。
这主要是因为,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果