Android布局加载之LayoutInflater示例详解
前言
Activity在界面创建时需要将XML布局文件中的内容加载进来,正如我们在ListView或者RecyclerView中需要将Item的布局加载进来一样,都是使用LayoutInflater来进行操作的。
LayoutInflater实例的获取有多种方式,但最终是通过(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来得到的,也就是说加载布局的LayoutInflater是来自于系统服务的。
由于Android系统源码中关于Content部分采用的是装饰模式,Context的具体功能都是由ContextImpl来实现的。通过在ContextImpl中找到getSystemService的代码,一路跟进,得知最后返回的实例是PhoneLayoutInflater。
registerService(Context.LAYOUT_INFLATER_SERVICE,LayoutInflater.class, newCachedServiceFetcher(){ @Override publicLayoutInflatercreateService(ContextImplctx){ returnnewPhoneLayoutInflater(ctx.getOuterContext()); }});
LayoutInflater只是一个抽象类,而PhoneLayoutInflater才是具体的实现类。
inflate方法加载View
使用LayoutInflater时常用方法就是inflate方法了,将一个布局文件ID传入并最后解析成一个View。
LayoutInflater加载布局的inflate方法也有多种重载形式:
Viewinflate(@LayoutResintresource,@NullableViewGrouproot) Viewinflate(@LayoutResintresource,@NullableViewGrouproot,booleanattachToRoot)
而这两者的差别就在于是否要将resource布局文件加载到root布局中去。
不过有点需要注意的地方,若root为null,则在xml布局中为resource设置的属性会失效,只是单纯的加载布局。
//temp是xml布局中的顶层View
finalViewtemp=createViewFromTag(root,name,inflaterContext,attrs);
ViewGroup.LayoutParamsparams=null;
if(root!=null){//root
//root不为null才会生成layoutParams
params=root.generateLayoutParams(attrs);
if(!attachToRoot){
//如果不添加到root中,则直接把布局参数设置给temp
temp.setLayoutParams(params);
}
}
//加载子View
rInflateChildren(parser,temp,attrs,true);
if(root!=null&&attachToRoot){
root.addView(temp,params);//添加到布局中,则布局参数用到addView中去
}
if(root==null||!attachToRoot){
result=temp;
}
跟进createViewFromTag方法查看View是如何创建出来的。
Viewview;//最后要返回的View
if(mFactory2!=null){
view=mFactory2.onCreateView(parent,name,context,attrs);//是否设置了Factory2
}elseif(mFactory!=null){
view=mFactory.onCreateView(name,context,attrs);//是否设置了Factory
}else{
view=null;
}
if(view==null&&mPrivateFactory!=null){//是否设置了PrivateFactory
view=mPrivateFactory.onCreateView(parent,name,context,attrs);
}
if(view==null){//如果的Factory都没有设置过,最后在生成View
finalObjectlastContext=mConstructorArgs[0];
mConstructorArgs[0]=context;
try{
if(-1==name.indexOf('.')){//系统控件
view=onCreateView(parent,name,attrs);
}else{//非系统控件,自定义的View
view=createView(name,null,attrs);
}
}finally{
mConstructorArgs[0]=lastContext;
}
}
如果设置过Factory接口,那么将由Factory中的onCreateView方法来生成View。
关于LayoutInflater.Factory的作用,就是用来在加载布局时可以自行去创建View,抢在系统创建View之前去创建。
关于LayoutInflater.Factory的使用场景,现在比较多的就是应用的换肤了。
若没有设置过Factory接口,则是判断是否为自定义控件或者系统控件,不管是onCreateView方法还是createView方法,内部最终都是调用到了createView方法,通过它来生成View。
//通过反射生成View的参数,分别是Context和AttributeSet类
staticfinalClass>[]mConstructorSignature=newClass[]{
Context.class,AttributeSet.class};
publicfinalViewcreateView(Stringname,Stringprefix,AttributeSetattrs)
throwsClassNotFoundException,InflateException{
Constructorconstructor=sConstructorMap.get(name);
Classclazz=null;
if(constructor==null){//从缓存中得到View的构造器,没有则调用getConstructor
clazz=mContext.getClassLoader().loadClass(
prefix!=null?(prefix+name):name).asSubclass(View.class);
if(mFilter!=null&&clazz!=null){
booleanallowed=mFilter.onLoadClass(clazz);
if(!allowed){
failNotAllowed(name,prefix,attrs);
}
}
constructor=clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name,constructor);
}else{
//Ifwehaveafilter,applyittocachedconstructor
if(mFilter!=null){//过滤,是否允许生成该View
//Haveweseenthisnamebefore?
BooleanallowedState=mFilterMap.get(name);
if(allowedState==null){
//Newclass--rememberwhetheritisallowed
clazz=mContext.getClassLoader().loadClass(
prefix!=null?(prefix+name):name).asSubclass(View.class);
booleanallowed=clazz!=null&&mFilter.onLoadClass(clazz);
mFilterMap.put(name,allowed);
if(!allowed){
failNotAllowed(name,prefix,attrs);
}
}elseif(allowedState.equals(Boolean.FALSE)){
failNotAllowed(name,prefix,attrs);//不允许生成该View
}
}
}
Object[]args=mConstructorArgs;
args[1]=attrs;
finalViewview=constructor.newInstance(args);//通过反射生成View
returnview;
在createView方法内部,首先从View的构造器缓存中查找是否有对应的缓存,若没有则生成构造器并且放到缓存中去,若有构造器则看能否通过过滤,是否允许该View生成。
最后都满足条件的则是通过View的构造器反射生成了View。
在生成View时采用Constructor.newInstance调用构造函数,而参数所需要的变量就是mConstructorSignature变量所定义的,分别是Context和AttributeSet。可以看到,在最后生成View时也传入了对应的参数。
采用Constructor.newInstance的形式反射生成View,是为了解耦,只需要有了类名,就可以加载出来。
由此可见,LayoutInflater加载布局仍然是需要传递Context的,不光是为了得到LayoutInflater,在反射生成View时同样会用到。
深度遍历加载布局
如果需要加载的布局只有一个控件,那么LayoutInflater返回那个View工作也就结束了。
若布局文件中有多个需要加载的View,则通过rInflateChildren方法继续加载顶层View下的View,最后通过rInflate方法来加载。
voidrInflate(XmlPullParserparser,Viewparent,Contextcontext,
AttributeSetattrs,booleanfinishInflate)throwsXmlPullParserException,IOException{
finalintdepth=parser.getDepth();
inttype;
//若while条件不成立,则加载结束了
while(((type=parser.next())!=XmlPullParser.END_TAG||
parser.getDepth()>depth)&&type!=XmlPullParser.END_DOCUMENT){
if(type!=XmlPullParser.START_TAG){
continue;
}
finalStringname=parser.getName();//从XmlPullParser中得到name出来解析
if(TAG_REQUEST_FOCUS.equals(name)){//name各种情况下的解析
parseRequestFocus(parser,parent);
}elseif(TAG_TAG.equals(name)){
parseViewTag(parser,parent,attrs);
}elseif(TAG_INCLUDE.equals(name)){
if(parser.getDepth()==0){
thrownewInflateException(" cannotbetherootelement");
}
parseInclude(parser,context,parent,attrs);
}elseif(TAG_MERGE.equals(name)){
thrownewInflateException(" mustbetherootelement");
}else{
finalViewview=createViewFromTag(parent,name,context,attrs);
finalViewGroupviewGroup=(ViewGroup)parent;
finalViewGroup.LayoutParamsparams=viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser,view,attrs,true);//继续遍历
viewGroup.addView(view,params);//顶层View添加子View
}
}
if(finishInflate){//遍历解析
parent.onFinishInflate();
}
}
rInflate方法首先判断是否解析结束了,若没有,则从XmlPullParser中加载出下一个View进行处理,中间还会对不同的类型进行处理,比如TAG_REQUEST_FOCUS、TAG_TAG、TAG_INCLUDE、TAG_MERGE等等。
最后仍然还是通过createViewFromTag来生成View,并以这个生成的View为父节点,开始深度遍历,继续调用rInflateChildren方法加载布局,并把这个View加入到它的父View中去。
至于为什么生成View的方法名字createViewFromTag从字面上来看是来自于Tag标签,想必是和XmlPullParser解析布局生成的内容有关。
总结
以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。