Android LayoutInflater深入分析及应用
LayoutInflater解析
前言:
在Android中,如果是初级玩家,很可能对LayoutInflater不太熟悉,或许只是在Fragment的onCreateView()中模式化的使用过而已。但如果稍微有些工作经验的人就知道,这个类有多么重要,它是连接布局XMl和Java代码的桥梁,我们常常疑惑,为什么Android支持在XML书写布局?
我们想到的必然是Android内部帮我们解析xml文件,LayoutInflater就是帮我们做了这个工作。
首先LayoutInflater是一个系统服务,这个我们可以从from方法看出来
/** *ObtainstheLayoutInflaterfromthegivencontext. */ publicstaticLayoutInflaterfrom(Contextcontext){ LayoutInflaterLayoutInflater= (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if(LayoutInflater==null){ thrownewAssertionError("LayoutInflaternotfound."); } returnLayoutInflater; }
通常我们拿到LayoutInflater对象之后就会调用其inflate方法进行加载布局,inflate是一个重载方法
publicViewinflate(intresource,ViewGrouproot){ returninflate(resource,root,root!=null); }
可以看到,我们调用2个参数的方法时候其默认是添加到父布局中的(父布局一般不为空)
publicViewinflate(intresource,ViewGrouproot,booleanattachToRoot){ finalResourcesres=getContext().getResources(); if(DEBUG){ Log.d(TAG,"INFLATINGfromresource:\""+res.getResourceName(resource)+"\"(" +Integer.toHexString(resource)+")"); } finalXmlResourceParserparser=res.getLayout(resource); try{ returninflate(parser,root,attachToRoot); }finally{ parser.close(); } }
这个方法中,其实是使用Resources将资源ID还原为XMlResoourceParser对象,然后调用inflate(XmlPullParserparser,ViewGrouproot,booleanattachToRoot)方法,解析布局的具体步骤都是在这个方法中实现
publicViewinflate(XmlPullParserparser,ViewGrouproot,booleanattachToRoot){ synchronized(mConstructorArgs){ Trace.traceBegin(Trace.TRACE_TAG_VIEW,"inflate"); finalAttributeSetattrs=Xml.asAttributeSet(parser); ContextlastContext=(Context)mConstructorArgs[0]; mConstructorArgs[0]=mContext; Viewresult=root; try{ //Lookfortherootnode. //1.循环寻找根节点,其实就是节点指针遍历的过程 inttype; while((type=parser.next())!=XmlPullParser.START_TAG&& type!=XmlPullParser.END_DOCUMENT){ //Empty } if(type!=XmlPullParser.START_TAG){ thrownewInflateException(parser.getPositionDescription() +":Nostarttagfound!"); } //2.得到节点的名字,用于判断该节点 finalStringname=parser.getName(); if(DEBUG){ System.out.println("**************************"); System.out.println("Creatingrootview:" +name); System.out.println("**************************"); } //3.对节点名字进行判断,然后是merge就将其添加到父布局中(依据Merge的特性必须添加到父布局中) if(TAG_MERGE.equals(name)){ if(root==null||!attachToRoot){ thrownewInflateException("<merge/>canbeusedonlywithavalid" +"ViewGrouprootandattachToRoot=true"); } rInflate(parser,root,attrs,false,false); }else{ //4.创建根据节点创建View //Tempistherootviewthatwasfoundinthexml finalViewtemp=createViewFromTag(root,name,attrs,false); ViewGroup.LayoutParamsparams=null; if(root!=null){ if(DEBUG){ System.out.println("Creatingparamsfromroot:"+ root); } //Createlayoutparamsthatmatchroot,ifsupplied //5.根据attrs生成布局参数 params=root.generateLayoutParams(attrs); if(!attachToRoot){ //Setthelayoutparamsfortempifwearenot //attaching.(Ifweare,weuseaddView,below) //6.如果View不添加到父布局中,那就给其本身设置布局参数 temp.setLayoutParams(params); } } if(DEBUG){ System.out.println("----->startinflatingchildren"); } //Inflateallchildrenundertemp //7.将该节点下的子View全部加载 rInflate(parser,temp,attrs,true,true); if(DEBUG){ System.out.println("----->doneinflatingchildren"); } //Wearesupposedtoattachalltheviewswefound(inttemp) //toroot.Dothatnow. //8.如果添加到父布局中,直接addView if(root!=null&&attachToRoot){ root.addView(temp,params); } //Decidewhethertoreturntherootthatwaspassedinorthe //topviewfoundinxml. //9.如果不添加到父布局,那么将自己返回 if(root==null||!attachToRoot){ result=temp; } } }catch(XmlPullParserExceptione){ InflateExceptionex=newInflateException(e.getMessage()); ex.initCause(e); throwex; }catch(IOExceptione){ InflateExceptionex=newInflateException( parser.getPositionDescription() +":"+e.getMessage()); ex.initCause(e); throwex; }finally{ //Don'tretainstaticreferenceoncontext. mConstructorArgs[0]=lastContext; mConstructorArgs[1]=null; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); returnresult; } }
重点的步骤我已经加上注释了,核心
1.找到根布局标签
2.创建根节点对应的View
3.创建其子View
我们从这里面可以看出来,子View的解析其实都是rInflate方法,如果xml中有根布局,那么就调用createViewFromTag创建布局中的根View。我们也可以明白merge的原来,因为它直接调用rInflate添加到父View中,看到rInflate(parser,root,attrs,false,false)和rInflate(parser,temp,attrs,true,true)第二个参数区别我们就明白了。
接下来我们看下rInflate如何创建多个布局
voidrInflate(XmlPullParserparser,Viewparent,finalAttributeSetattrs, booleanfinishInflate,booleaninheritContext)throwsXmlPullParserException, IOException{ //获取当前解析器指针所在节点处于布局层次 finalintdepth=parser.getDepth(); inttype; //进行树的深度优先遍历(如果一个节点有子节点将会再次进入rInflate,否则继续循环) while(((type=parser.next())!=XmlPullParser.END_TAG|| parser.getDepth()>depth)&&type!=XmlPullParser.END_DOCUMENT){ if(type!=XmlPullParser.START_TAG){ continue; } finalStringname=parser.getName(); //如果其中有request_focus标签,那就给这个节点View设置焦点 if(TAG_REQUEST_FOCUS.equals(name)){ parseRequestFocus(parser,parent); //如果其中有tag标签,那就给这个节点View设置tag(key,value) }elseif(TAG_TAG.equals(name)){ parseViewTag(parser,parent,attrs); }elseif(TAG_INCLUDE.equals(name)){ //如果其中是include标签,如果include标签 if(parser.getDepth()==0){ thrownewInflateException("<include/>cannotbetherootelement"); } parseInclude(parser,parent,attrs,inheritContext); }elseif(TAG_MERGE.equals(name)){ thrownewInflateException("<merge/>mustbetherootelement"); }else{ //创建该节点代表的View并添加到父view中,此外遍历子节点 finalViewview=createViewFromTag(parent,name,attrs,inheritContext); finalViewGroupviewGroup=(ViewGroup)parent; finalViewGroup.LayoutParamsparams=viewGroup.generateLayoutParams(attrs); rInflate(parser,view,attrs,true,true); viewGroup.addView(view,params); } } //代表着一个节点含其子节点遍历结束 if(finishInflate)parent.onFinishInflate(); }
从上面可以看到,所以创建View都将会交给createViewFromTag(Viewparent,Stringname,AttributeSetattrs,booleaninheritContext)中,我们可以看下该方法如何创建View
ViewcreateViewFromTag(Viewparent,Stringname,AttributeSetattrs,booleaninheritContext){ if(name.equals("view")){ name=attrs.getAttributeValue(null,"class"); } ContextviewContext; if(parent!=null&&inheritContext){ viewContext=parent.getContext(); }else{ viewContext=mContext; } //Applyathemewrapper,ifrequested. finalTypedArrayta=viewContext.obtainStyledAttributes(attrs,ATTRS_THEME); finalintthemeResId=ta.getResourceId(0,0); if(themeResId!=0){ viewContext=newContextThemeWrapper(viewContext,themeResId); } ta.recycle(); if(name.equals(TAG_1995)){ //Let'spartylikeit's1995! returnnewBlinkLayout(viewContext,attrs); } if(DEBUG)System.out.println("********Creatingview:"+name); try{ Viewview; if(mFactory2!=null){ view=mFactory2.onCreateView(parent,name,viewContext,attrs); }elseif(mFactory!=null){ view=mFactory.onCreateView(name,viewContext,attrs); }else{ view=null; } if(view==null&&mPrivateFactory!=null){ view=mPrivateFactory.onCreateView(parent,name,viewContext,attrs); } if(view==null){ finalObjectlastContext=mConstructorArgs[0]; mConstructorArgs[0]=viewContext; try{ if(-1==name.indexOf('.')){ view=onCreateView(parent,name,attrs); }else{ view=createView(name,null,attrs); } }finally{ mConstructorArgs[0]=lastContext; } } if(DEBUG)System.out.println("Createdviewis:"+view); returnview; }catch(InflateExceptione){ throwe; }catch(ClassNotFoundExceptione){ InflateExceptionie=newInflateException(attrs.getPositionDescription() +":Errorinflatingclass"+name); ie.initCause(e); throwie; }catch(Exceptione){ InflateExceptionie=newInflateException(attrs.getPositionDescription() +":Errorinflatingclass"+name); ie.initCause(e); throwie; } }
其实很简单,就是4个降级处理
if(factory2!=null){ factory2.onCreateView(); }elseif(factory!=null){ factory.onCreateView(); }elseif(mPrivateFactory!=null){ mPrivateFactory.onCreateView(); }else{ onCreateView() }
其他的onCreateView我们不去设置的话为null,我们看下自己的onCreateView(),其实这个方法会调用createView()
publicfinalViewcreateView(Stringname,Stringprefix,AttributeSetattrs) throwsClassNotFoundException,InflateException{ //从构造器Map(缓存)中获取需要的构造器 Constructor<?extendsView>constructor=sConstructorMap.get(name); Class<?extendsView>clazz=null; try{ Trace.traceBegin(Trace.TRACE_TAG_VIEW,name); if(constructor==null){ //Classnotfoundinthecache,seeifit'sreal,andtrytoaddit //如果缓存中没有需要的构造器,那就通过ClassLoader加载需要的类 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); sConstructorMap.put(name,constructor); }else{ //Ifwehaveafilter,applyittocachedconstructor if(mFilter!=null){ //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); } } } Object[]args=mConstructorArgs; args[1]=attrs; constructor.setAccessible(true); //通过反射获取需要的实例对象 finalViewview=constructor.newInstance(args); if(viewinstanceofViewStub){ //UsethesamecontextwheninflatingViewStublater. finalViewStubviewStub=(ViewStub)view; //ViewStub将创建一个属于自己的LayoutInflater,因为它需要在不同的时机去inflate viewStub.setLayoutInflater(cloneInContext((Context)args[0])); } returnview; }catch(NoSuchMethodExceptione){ InflateExceptionie=newInflateException(attrs.getPositionDescription() +":Errorinflatingclass" +(prefix!=null?(prefix+name):name)); ie.initCause(e); throwie; }catch(ClassCastExceptione){ //IfloadedclassisnotaViewsubclass InflateExceptionie=newInflateException(attrs.getPositionDescription() +":ClassisnotaView" +(prefix!=null?(prefix+name):name)); ie.initCause(e); throwie; }catch(ClassNotFoundExceptione){ //IfloadClassfails,weshouldpropagatetheexception. throwe; }catch(Exceptione){ InflateExceptionie=newInflateException(attrs.getPositionDescription() +":Errorinflatingclass" +(clazz==null?"<unknown>":clazz.getName())); ie.initCause(e); throwie; }finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
大体步骤就是,
1.从缓存中获取特定View构造器,如果没有,则加载对应的类,并缓存该构造器,
2.利用构造器反射构造对应的View
3.如果是ViewStub则复制一个LayoutInflater对象传递给它
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!