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开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。