初始化
常用的方法如下:
LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
通过getSystemService()可知,LayoutInflater的子类是PhoneLayoutInflater。
PhoneLayoutInflater
主体如下:
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
//构造方法,略
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
//createView的主要作用就是将prefix+name形成一个完整的类名,然后通过反射生成具体的实例。后面再说。
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
}
}
return super.onCreateView(name, attrs);
}
从这个类中定义的sClassPrefixList中可以看出,使用这几个包下的View是不用写包名的。这也是为什么我们在xml中可以直接使用TextView,而自定义时却需要写完整包名的原因——因为TextView的包名由系统自动添加上了,自己的却不行。
LayoutInflater#inflate()
任何一个inflate最终会调用到下面的inflate。注:采用的是pull解析xml布局。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//略
final Context inflaterContext = mContext;//mContext为构造方法中传入的Context对象
final AttributeSet attrs = Xml.asAttributeSet(parser);
//记录context,略
View result = root;//记录根节点的父节点,下简称父节点。
try {
// 查找根节点,并使用name记录根节点的名
int type;
//查找过程略,pull解析中的部分
final String name = parser.getName();
//如果根节点是merge,就调用rInflate()。
if (TAG_MERGE.equals(name)) {
//调用之前有一个判断,用来保证merge必须有一个root且attachToRoot为true
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 调用createViewFromTag()建立根节点,并用temp记录根节点。这里的inflaterContext就是构造方法中传入的Context对象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//为根结点生成LayoutParams。略。
// 填充根节点下的子View
rInflateChildren(parser, temp, attrs, true);
// 有父节点,并且要添加到父节点中时,把根节点添加到父节点
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 根节点找不到父节点或者不需要添加到父节点中时,返回布局的根节点
if (root == null || !attachToRoot) {
result = temp;
}
}
}catch(){
//一堆的异常处理,略
}
//root为null,或者attachToRoot为false,则返回的是布局生成的根节点。
return result;
}
}
主体的逻辑见注释。
整个过程涉及到三个方法:createViewFromTag()——根据tag生成对应的view;rInflateChildren()——用来填充子布局;rInflate()——生成merge布局。
createViewFromTag()
这里的tag就是xml布局中指定类,也就是传入其中的name。其内部有用工厂模式,生成相应的View实例。如下:
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {//context为LayoutInflater构造方法中传入的Context对象。
//略
try {
//采用工厂直接生成。基本上都可忽略,但可以通过setFactory设置解析工厂,定制自己的解析
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {//各个工厂都生成不了,自己调用相应的方法进行生成
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;//mConstructorArgs是最终传入到View两参数构造方法中的第一个Coontext变量
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} //异常处理,略
}
上面的一些工厂类,默认时都是为null,所以工厂解析这段可以直接略去。之所以添加这个判断,是为了扩展。在使用时可以通过LayoutInflater#setFactory()设置不同的工厂类,定制自己的解析类。
所以整个生成view的过程都集中在了onCreateView与createView中。从PhoneLayoutInflater#onCreateView()中可以发现,onCreateView最终调用的还是createView——包括LayoutInflater#onCreateView()也是一样。
并且传入到createView中的第二个参数为当前类的包名+".",这个参数可以和tag组合成一个完整的类名,然后可以通过反射的方法生成对应的类——这也是onCreateView的作用(凑成完整的类名,方便使用反射)。
createView
如下:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor extends View> constructor = sConstructorMap.get(name);
Class extends View> clazz = null;
try {
if (constructor == null) {
// 该类从来没有被加载过,需要用classloader去加载类的字节码文件,不然下面的反射都没办法做
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//判断是否不允许加载inflater某个类
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);//这里直接扔一个异常。因为布局中使用了不允许使用的类
}
}
//mConstructorSignature的值为:new Class[] {Context.class, AttributeSet.class};
//这也是为什么xml中的控件执行的构造函数都是两个参数的
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);//与第一行代码合用,避免了对类的重复加载,提升性能
} else {
//对加载的类通过mFilter进行过滤,一般不执行,略。
}
Object[] args = mConstructorArgs;//第一个参数在createViewFromTag中已经指定了,为创建LayoutInflater时传入的Context对象
args[1] = attrs;
final View view = constructor.newInstance(args);//反射生成相应的View实例
if (view instanceof ViewStub) {
//ViewStub单独处理
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} //catch块略
}
主体逻辑见注释。
这个方法执行完毕之后,就可以根据传入的name与prefix生成一个对应的对象。
rInflateChildren()
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
一样调用的是rInflate()。只不过最后一个参数为true。
rInflate()
有一点要注意:解析merge时,最后一个参数为false;解析根节点下的view是最后一个参数为true。
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) {
//获取布局中的tag,并使用name存储。
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) {
throw new InflateException(" cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException(" must be the root element");
} else {//如果是正常的View,调用createViewFromTag生成View实例
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);//递归,生成所有的View实例,并添加到ViewGroup中
viewGroup.addView(view, params);//并将生成的实例添加到parent中。
}
}
if (finishInflate) {//整个布局加载完毕,调用根布局的onFinishInflate()方法。
parent.onFinishInflate();
}
}
逻辑见注释。
而且可以得出:对于一个ViewGroup,其子View都生成完毕之后会调用它的onFinishInflate(),但要注意的是此时子View并没有width与height。
总结
上面就是LayoutInflater的整体过程。从中可以发现:
1,可以通过setFactory()设置自己的解析工厂类。
2,LayoutInflater是单例的。
3,整体执行流程为:inflate()->createViewFromTag()->分两种情况:1)没有factory。如果是系统View,则走onCreateView,获取View的全类名前缀后再走createView。如果是自定义View,则直接走createView。因此,在createView中,一定是可以拼出View全名。2)有factory,则执行factory#onCreateView()——此时传入的name并不完整。因此,在自定义解析类时,需要在factory#onCreateView()中调用createView()生成相应的View实例,必要时需要仿制PhoneLayoutInflater#onCreateView。
得到view实例后,可以根据view的配置参数进行相应的设置。比如可以调用View#setTag()为view设置一个在布局中指定的tag值。
4,LayoutInflater的主要作用就是根据xml文件生成文件中指定的各个View的实例,在此过程中会调用含有两个参数的构造函数,但不会对View执行任何measure、layout与draw操作。
自定义
class My extends LayoutInflater {
private LayoutInflater inflater;
protected My(LayoutInflater original, Context newContext) {
super(original, newContext);
inflater = original;
Log.e("TAG","original fa = "+original.getFactory()+","+inflater.getFactory2());
setFactory2(new MyF(original));//这里设置的f2,不能直接使用setFactory()。如果使用了在AppCompatActivity中是没有效果的。
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
LayoutInflater result = new My(inflater,newContext);
result.setFactory2(new MyF(inflater));
return result;
}
private class MyF implements Factory2 {
private LayoutInflater inflater;
private final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
private MyF(LayoutInflater inflater) {
this.inflater = inflater;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = null;
try {
if (-1 == name.indexOf('.')) {//这里的name就是传入onCreateView中的name
view = onCreateView(name, attrs);
} else {
view = createView(name, null, attrs);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(view instanceof TextView){
((TextView)view).setText("My = "+ My.this);
}
return view;
}
private View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
//empty
}
}
return null;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return onCreateView(name,context,attrs);
}
}
}
其中唯一一点要注意的地方就是调用的是setFactory2(),而不能直接使用setFactory。如果Activity直接继承于AppCompatActivity的话,它会为inflater添加上factory2对象,这样自己设置的factory是没有效果的。