一、xml加载流程:
Activity
AppCompatActivity
Activity.setContentView流程(android 30)
二、LayoutInflate的参数的作用
// 方式一:将布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, ll, true);
// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true); // 已经addView
ll.addView(view);
// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);
// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);
三、描述下merge、include、ViewStub标签的特点
- include:
- 不能作为根元素,需要放在 ViewGroup中
- findViewById查找不到目标控件,这个问题出现的前提是在使用include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。
- 为什么会报空指针呢?
如果使用include标签时设置了id,这个id就会覆盖 layout根view中设置的id,从而找不到这个id
代码:LayoutInflate.parseInclude
–》final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
–》if (id != View.NO_ID) {
view.setId(id);
}
- 为什么会报空指针呢?
- merge:
- merge标签必须使用在
根布局
- 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
- 由于merge不是View,所以对merge标签设置的所有属性都是无效的.
- ViewStub: 就是一个
宽高都为0
的一个View,它默认是不可见
的
- 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了
viewStub.inflate()
;或者viewStub.setVisible(View.visible)
方法时才内容才变得可见。 - 这里需要注意的一点是,当ViewStub被inflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,而是使用对应的layout视图代替。
ViewStub.java
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// 获取 InflatedId
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 设置不显示
setVisibility(GONE);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置宽高为0
setMeasuredDimension(0, 0);
}
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 设置ID
final View view = inflateViewNoAdd(parent);
// 替换自己和View
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
private View inflateViewNoAdd(ViewGroup parent) {
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 移除自己
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 添加View
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
四、相关面试题
-
为什么requestWindowFeature()要在setContentView()之前调用
答:requestWindowFeature 实际调用的是 PhoneWindow.requestFeature,
在这个方法里面会判断如果变量 mContentParentExplicitlySet 为true则报错,而这个变量会在PhoneWindow.setContentView
调用的时候设置为true。 -
为什么这么设计呢?
答:DecorView的xml布局是通过设置的窗口特征进行选择的。 -
为什么Activity继承AppCompatActivity中使用requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
答:需要用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE);,因为继承的是AppCompatActivity,这个类里面会覆盖设置。 -
自定义view为什么必须要定义两参的构造方法
答:一个参数的构造方法是在new的时候使用
两个参数的构造方法是在xml解析过程中使用,通过反射进行调用
LayoutInflate.java
@UnsupportedAppUsage
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class}; // 这个是两个参数,所以自定义view的时候必须要定义两个参数的构造函数
@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
...
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
// 通过反射创建View对象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
// 拿构造对象
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
...
}