关于LayoutInflater
参考郭霖大神公众号文章《再看LayoutInflater,这次你可能又会有新的认识》
LayoutInflater:将使用xml文件编写的布局转换成Android里的View对象
在Activity中经常调用setContentView()
方法来加载布局,但可以从该方法的源码看出来,底层同样是使用了LayoutInflater
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
简要概括一下LayoutInflater将xml布局转换成一个View对象的过程:
- 通过解析器来将xml文件中的内容解析出来
// 关键部分代码
public View inflate(@LayoutRes int resource,
@Nullable ViewGroup root,
boolean attachToRoot) {
...
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
- 使用反射将解析出来的元素创建成View对象
public final View createView(@NonNull Context viewCOntext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throw ClassNotFoundException, InflateException {
...
if (constructor == null) {
clazz = Class.forName(prefix != null ? (prefix + name) : name, flase,
mContext.getClassLoader()).asSubclass(View.class);
consturctor = clazz.getConstructor(mConstructorSignature);
consturctor.setAccessible(true);
sConstructorMap.put(name, constructor);
}
...
try {
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub)view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
}
}
了解LayoutInflater用法层面:
LayoutInflater常见用法:View view = LayoutInflater.from(context).inflate(resourceId, parent, flase);
public View inflate(int resource,
@Nullable ViewGroup root,
boolean attachToRoot) {}
inflate()
方法最难理解的参数就是root和attachToRoot
首先Android布局结构是一种树状结构(布局包含子布局,子布局由可以继续包含子布局)
每个布局都有一个父布局,第二个参数root就是给当前解析加载的xml布局指定一个父布局,当然,一个布局可以没有父布局,可以从root参数的注解@Nullable看出,但多数情况下都要指定父布局
但如果没有父布局就没有办法展示出来,只能之后使用addView的方式将其添加到一个现有的布局下或者这个布局就是一个顶层布局
如果不指定父布局还有出现一个问题:layout_width和layout_height两个属性失去作用,从名称上可以看出这两个值主要是用于设置View在布局中大小,所以必须存在于一个布局中
而对于attachToRoot参数从名称上可以看出,这个参数在问是否要添加到root上面,也就是问是否将当前加载的xml布局添加到第二个参数传入的父布局上
如果传入false,那么当前加载的xml布局就不会添加到root上,可以在之后调用addView添加
在Fragment中加载一个布局通常会这么写
public class MyFragment extends Fragment {
@Override
public view onCreate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_layout, container, false);
}
}
此处inflater()方法传入的第三个参数值为false,因为此处返回的View会被添加到一个Container中
void addViewToContainer() {
int index = mFragmentStore.findFragmentIndexInContainer(mFragment);
mFragmnet.mContainer.addView(mFragment.mView, index);
}
后续Fragment会纪念性一个addView操作,如果上面传入的第三个参数为true,则会崩溃,因为子布局已经有父布局了,不能再添加到别的布局上了
在RecyclerView中LayoutInflater用法和Fragment一样,传入的第三个参数也是false,原因也是入此