Android开发知识(二十二)LayoutInflater装载xml布局过程的源码解析
文章目录
- 前言
- LayoutInflater实例
- LayoutInflater的装载过程
- include 标签解析
- merge 标签解析
- attachToRoot参数解析
- View创建过程
- (1)判断view标签
- (2) 主题相关判断
- (3)BlinkLayout判断
- (4)自定义View的创建规则
- (5)View的默认创建规则
- (6)View的创建过程
- ViewStub源码解析
- 结束语
前言
本篇讲解的是LayoutInflater的装载过程,其中会涉及到include、merge、ViewStub标签的源码解析。
我们对LayoutInflater的使用是再熟悉不过了,日常操作都是先调用from方法然后调用inflate方法。而对LayoutInflater是如何把一个xml布局文件加载到界面上的过程可能不是很了解。
LayoutInflater实例
说起LayoutInflater,我们最熟悉的写法是:
LayoutInflater.from(context).inflate(R.layout.xx,null);
LayoutInflater的from方法源码是:
public static LayoutInflater from(Context context) {LayoutInflater LayoutInflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);if (LayoutInflater == null) {throw new AssertionError("LayoutInflater not found.");}return LayoutInflater;}
可以看出实际上是调用context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
而LayoutInflater本身是一个抽象类,它的具体实现类是在ContextImpl的getSystemService方法中被实例化。
通常在getSystemService方法得到的Object都是来自于SystemServiceRegistry中的static代码块中去registerService的:
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher<LayoutInflater>() {@Overridepublic LayoutInflater createService(ContextImpl ctx) {return new PhoneLayoutInflater(ctx.getOuterContext());}});
由此可以知道,我们用的LayoutInflater实例都是来自于PhoneLayoutInflater,并且from方法并不会创建一个新的实例。
但是一看PhoneLayoutInflater并没有几行代码,由此判断基本逻辑都是在LayoutInflater这个抽象类中,我们只要关注LayoutInflater就行了
LayoutInflater的装载过程
那它是怎么装载布局的呢?我们调用inflate方法,经过重载后会到它是三个参数的重载方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}//载入布局文件final XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}
经过这个方法把xml文件载入进来,XmlResourceParser继承了XmlPullParser,可以看出解析xml布局是基于Pull解析的(不了解XML几个解析方式的最好先自行百度下),最终重载调用到这个inflate方法(只举例部分重要代码):
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {...final Context inflaterContext = mContext;//XmlResourceParser也是实现了AttributeSet接口,转成AttributeSet后相当于对属性的一种封装,方便属性的获取。final AttributeSet attrs = Xml.asAttributeSet(parser);View result = root;...final String name = parser.getName();//根标签是否是merge标签if (TAG_MERGE.equals(name)) {//merge标签的布局必须传入父view,并且attachToRoot=true,不然xml中零零散散的view不知道要被添加到哪里if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}//解析带merge标签的布局,注意最后一个参数是传递false,传的Context是inflaterContext,也就是LayoutInflater的mConext,实际上就是LayoutInflater.from(context)时传的contextrInflate(parser, root, inflaterContext, attrs, false);} else{//创建布局的根结点,createViewFromTag方法里会解析出对应的类名,然后调用createView方法反射创建出来final View temp = createViewFromTag(root, name, inflaterContext, attrs);//生成根布局的布局参数对象params = root.generateLayoutParams(attrs);//解析子View布局,注意最后一个参数是传递truerInflateChildren(parser, temp, attrs, true);//如果我们传入了一个parent View,那么就把根结点附着到这个parent上if (root != null && attachToRoot) {root.addView(temp, params);}}...}
接下来rInflateChildren,用来装载子布局的,实际是调用了rInflate方法:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,boolean finishInflate) throws XmlPullParserException, IOException {//注意这里的context有区别于merge标签时传的context了rInflate(parser, parent, parent.getContext(), attrs, finishInflate);}
到这里有个重要的发现,就是在inflate中判断了是不是merge布局,最终都会走向rInflate方法,
不同的是 如果是merge布局,那么rInflate方法中的第二个参数传递的是root,也就是来自我们调用的inflate(R.layout.xx,root,true);,然后最后一个参数finishInflate是false,而且context是来自于from(context)
如果不是merge布局。那么的第二个参数传递的是temp,也就是调用createViewFromTag后解析出根布局的view,最后一个参数finishInflate传的是true,Context传的是View的getContext()
那么rInflate方法如下:
void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {//获取xml标签的层级final int depth = parser.getDepth();int type;//pull解析结束条件while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {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("<include /> cannot be the root element");}parseInclude(parser, context, parent, attrs);} else if (TAG_MERGE.equals(name)) {//merge标签只能作为xml的根标签,也就是在另外一个文件中throw new InflateException("<merge /> must be the root element");} else {//递归解析创建View or ViewGroupfinal View view = createViewFromTag(parent, name, context, attrs);final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflateChildren(parser, view, attrs, true);viewGroup.addView(view, params);}}//xml装载完成后每个view都会收到onFinishInflate方法的回调if (finishInflate) {parent.onFinishInflate();}}
我们关注else分支的代码,这里面也就是一个递归,不断的调用createViewFromTag反射创建出View,然后把创建的View添加到传进来的Parent View上,
在前面我们说的如果是merge布局的话则需要传一个root,因为merge布局文件中是没有父布局的,需要有个父布局可以来装载View
而如果不是merge的话,则传进来的是xml布局的根布局view。
include 标签解析
在里面中,判断遇到include 标签,则判断parser.getDepth() >0 就调用parseInclude方法
parser.getDepth()方法说明一下,指的也就是标签的层级(深度)
比如有下面的XML片段:
<root><parent><child><child/><parent/>
<root/>
那么root就是第一层 parent就是第二层 child就是第三层
这里代码判断parser.getDepth()==0要抛出异常,说明 include标签是不能作为XML中的根标签的,换一种意思就是,include标签外面必须有包含的ViewGroup(当然这里还没体现出来一定得是ViewGroup,但在parseInclude方法里就判断了这个)
不过这里有点废话了 ,我们使用include一定是嵌在某个父布局里,要是XML要想就单单使用include的内容的话,那还不如直接引用那个xml文件了 何必新增个xml文件
在parseInclude方法与inflate方法有很大类似的逻辑,因为本身include标签也是去加载另外一个xml布局
parseInclude方法中比较长,我们挑几个重要的流程代码:
private void parseInclude(XmlPullParser parser, Context context, View parent,AttributeSet attrs) throws XmlPullParserException, IOException {//当然 能用inclde标签的一定是ViewGroupif (parent instanceof ViewGroup) {...int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);if (layout == 0) {final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);//如果include中没有带layout标签,则抛出异常。没有layout标签的include是没有灵魂的,连肉体都没有if (value == null || value.length() <= 0) {throw new InflateException("You must specify a layout in the"+ " include tag: <include layout=\"@layout/layoutID\" />");}...}else{//跟inflate方法类似,会吧layout读取进来final XmlResourceParser childParser = context.getResources().getLayout(layout);//剩下的代码与inflate过程有惊人的相似,这里不再说明...}}}
另外注意rInflate方法最下面的:
if (finishInflate) {parent.onFinishInflate();}
如果finishInflate为true,则会调用View的onFinishInflate方法。
因为rInflate方法是递归调用的,由此得知onFinishInflate方法的调用时机就是:在整个XML布局解析完毕之后会回调每个View的onFinishInflate方法
通常我们自定义View的时候时就可以用到onFinishInflate方法来监听布局是否已经加载完成,以确保我们可以读取到View和属性,比如在XML使用自定义ViewGroup时,它的getChildAt()、findViewById()一定是在onFinishInflate方法之后使用
merge 标签解析
merge 标签解析并没有太多与其他xml不同的地方。
上面代码注释说了如果是带merge标签的xml则在进入rInflate方法时传了finishInflate为false,这不是不让merge标签里的View回调onFinishInflate
因为递归调用rInflateChildren时都一样是传true
这里传的false只是针对于载入merge标签的父布局,也就是root。为什么要为false呢?
因为使用merge标签有两种情况:
第一种是我们自定义组合视图时,比如LinearLayout。在里面进行inflate时一般会采用merge标签的xml,因为本身就是一个父布局了
另外一种就是XML中与include标签组合使用,比如我们定义一个merge标签的视图(注意此时是没有根布局的),然后在其它xml里面要引用这个merge的话
可以使用include把该xml包含进来,这样就达到了复用带merge标签里的布局内容,又不会因为要include一个xml进来时而多了一层父布局。
在第一种情况下,我们其实用不到finishInflate这个回调,因为调用完inflate方法后我们就可以使用findViewById了
但是第二种情况下,对于merge的父布局来讲本身就会调用被一次finishInflate方法了,那么在遇到merge时,对rInflate传入这个父布局要是再调用就重复了。
attachToRoot参数解析
到这里,还有个东西没说,就是我们的inflate方法:
LayoutInflater.from(context).inflate(R.layout.root,false);
第三个参数是啥作用?或许你已经明白,但是我们还是从源码的角度来解释它的作用
在上面,我并没有贴出inflate方法中关于第三个参数的相关代码,是因为想单独出来说,下面我们抽出这个方法涉及到这个参数的代码:
//这里我们讲过,merge标签必须要attachToRoot为true,为什么呢
if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, inflaterContext, attrs, false);
}params = root.generateLayoutParams(attrs);//如果attachToRoot不为tue,则xml根布局才会设置LayoutParamsif (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)temp.setLayoutParams(params);}//如果我们有指定传入一个父布局,并且attachToRoot=true ,那么会把xml根布局添加到这个我们指定的父布局中if (root != null && attachToRoot) {root.addView(temp, params);}//如果我们没指定一个父布局 或者attachToRoot=false,则inflate方法解析完成后返回的view就是这个xml的根布局if (root == null || !attachToRoot) {result = temp;}
经过上面对几种情况的注释,相信已经差不多了解attachToRoot的影响了吧。
attachToRoot的作用也就是要不要附属到指定的父布局上
如果我们传入的root不为空,并且attachToRoot为true,则会把布局内容添加到root容器里
如果我们传入的root不为空,并且attachToRoot为false,那么同样不会添加到root容器里,但是也不会设置自身的LayoutParams
如果我们传入的root为空,那么attachToRoot为true,布局内容不会被添加到其他地方去,但是会设置自身的LayoutParams
如果我们传入的root为空,那么attachToRoot为false,布局内容不会被添加到其他地方去,同时也不会设置自身的LayoutParams
View创建过程
到这里我们已经对LayoutInflater在装载View的过程有个大概的了解了。
我们在其中一个叫做createViewFromTag的方法在上面都是一笔带过,这里挑出来专门大概讲一下。
createViewFromTag方法最终调用它的5个参数的重载方法:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {// 1if (name.equals("view")) {name = attrs.getAttributeValue(null, "class");}// 2if (!ignoreThemeAttr) {final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);final int themeResId = ta.getResourceId(0, 0);if (themeResId != 0) {context = new ContextThemeWrapper(context, themeResId);}ta.recycle();}//3if (name.equals(TAG_1995)) {// Let's party like it's 1995!return new BlinkLayout(context, attrs);}try {View view;//4if (mFactory2 != null) {view = mFactory2.onCreateView(parent, name, context, attrs);} else if (mFactory != null) {view = mFactory.onCreateView(name, context, attrs);} else {view = null;}//5if (view == null && mPrivateFactory != null) {view = mPrivateFactory.onCreateView(parent, name, context, attrs);}//6if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) {view = onCreateView(parent, name, attrs);} else {view = createView(name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}}return view;} catch (InflateException e) {throw e;} catch (ClassNotFoundException e) {final InflateException ie = new InflateException(attrs.getPositionDescription()+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(attrs.getPositionDescription()+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;}}
我在源码中标注了6个地方,一个一个描述
(1)判断view标签
从源码上看,当标签是view的话,就获取它的一个class属性,然后重新赋值给name
由此我们可以知道,这个view标签的用法应该是这样的:
<viewclass="LinearLayout"android:layout_width="match_parent"android:layout_height="match_parent"/>
想这样的话,name本身是view,而解析了class属性后,name就变成了LinearLayout,与我们定义:
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"/>
作用是一样的。这个view标签我们几乎不会用到这样的写法,所以可能很多人还不知道view标签的作用
(2) 主题相关判断
从源码逻辑上看,是获取了theme的相关属性,如果有的话则重新包装一下Context,变成一个带主题的Context,也就是ContextThemeWrapper
这个主题会影响后面关于View的属性样式相关
(3)BlinkLayout判断
blink标签是一个会自动闪烁的布局容器,会被转化成BlinkLayout。
如果你有兴趣的话可以写个例子:
<blinkandroid:layout_width="wrap_content"android:layout_height="wrap_content"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="我会一直闪烁的文字" /></blink>
BlinkLayout是LayoutInflater里面的一个私有内部类,我们并不能直接使用它,而且我们日常也几乎用不到这个blink标签
它的内部也没有几句代码,点击进去看一下只是继承了下FrameLayout
内部封装了一个Handler用于定时调用invalidate、然后用一个不断交替的mBlinkState变量判断是否调用dispatchDraw来完成绘制
(4)自定义View的创建规则
这个从代码看上去貌似是用来生产View的工厂,那么mFactory和mFactory2是什么时候被赋值的
在AS上输入查找关键词: mFactory =
可以看到他们分别都有一个set方法:setFactory(Factory factory) 和 setFactory2(Factory2 factory)
setFactory如下:
public void setFactory(Factory factory) {if (mFactorySet) {throw new IllegalStateException("A factory has already been set on this LayoutInflater");}if (factory == null) {throw new NullPointerException("Given factory can not be null");}mFactorySet = true;if (mFactory == null) {mFactory = factory;} else {mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);}}
可以发现setFactory方法并不允许被重复调用的,不然会抛出异常
然后mFactory如果不为空的话,并没有直接赋值传进来的factory变量,而是创建了一个FactoryMerger
看setFactory2方法如下:
public void setFactory2(Factory2 factory) {if (mFactorySet) {throw new IllegalStateException("A factory has already been set on this LayoutInflater");}if (factory == null) {throw new NullPointerException("Given factory can not be null");}mFactorySet = true;if (mFactory == null) {mFactory = mFactory2 = factory;} else {mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);}}
可以发现setFactory2方法也是不允许被重复调用的,不然会抛出异常
而且逻辑也一样,mFactory如果不为空的话,并没有直接赋值传进来的factory变量,而是创建了一个FactoryMerger。
那么FactoryMerger是啥呢?从名字上看待了merge的字眼,看上去不难猜测出是合并这两个工厂的作用。
根据Factory接口的介绍我们明白了,Factory接口和Factory2接口都是用来给开发者自定义View的创建规则的。
而Factory2接口相比Factory接口,则重载了下接口方法,多传了个parent View进来
Factory2接口是在API 11的时候被加入进来的,因为Factory接口造成的问题是我们无法得知它的父 view是谁 从而限制了一些操作。
所以官方已经废弃了Factory接口,我们一般要采用的就是Factory2接口
关于这个Factory接口,谷歌是这么介绍的:
/*** Hook you can supply that is called when inflating from a LayoutInflater.* You can use this to customize the tag names available in your XML* layout files.***/
原来是做了个hook方便我们来自定义View的创建规则,比如说我故意要把该xml布局文件里面的TextView控件都换成Button
那么我们可以在Activity内这么写:
LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {return null;}@Overridepublic View onCreateView(String name, Context context, AttributeSet attrs) {if(name.equals("TextView")){Button button = new Button(context,attrs);return button;}return null;}});
既然是留给开发者自定义的,那么正常来讲这里factory和factory2都会为空,所以view还是空的
(5)View的默认创建规则
正常来说的话则会走第5步这个流程:
//5if (view == null && mPrivateFactory != null) {view = mPrivateFactory.onCreateView(parent, name, context, attrs);}
mPrivateFactory一看就是私有的工厂 不是给开发者用的。那么这个mPrivateFactory是什么时候被赋值的呢,搜索一下发现:
/*** @hide for use by framework*/public void setPrivateFactory(Factory2 factory) {if (mPrivateFactory == null) {mPrivateFactory = factory;} else {mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);}}
framework会自己调用这个共有方法,而对开发者是隐藏的。
它在Activity的attach方法中被用到:
final void attach(...){mWindow.getLayoutInflater().setPrivateFactory(this);}
设置了Activity自己,看来Activity是实现了这个接口了,找了下发现Activity实现了LayoutInflater.Factory2接口
我们看Activity是如何实现这个接口的:
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {if (!"fragment".equals(name)) {return onCreateView(name, context, attrs);}return mFragments.onCreateView(parent, name, context, attrs);}
@Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {return null;
}public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {return mHost.mFragmentManager.onCreateView(parent, name, context, attrs);}
这里一不小心引出了Fragment。
我们可以发现Activity并没有自己根据标签去创建View,而是判断如果是fragment标签的话则应用是走了Fragment的onCreateView生命周期方法
所以我们就可以知道,如果是fragment标签的话,则是Activity自己创建的
如果不是的话,Activity调用3个参数的onCreateView方法,直接返回null
所以默认情况下,还是会教给LayoutInflater自己来创建
(6)View的创建过程
既然我们得出了上面的结论,那么下面这个第6步的代码,默认情况下还是会走
// 6
if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) {view = onCreateView(parent, name, attrs);} else {view = createView(name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}
这里做了个判断,判断标签名字是否有带个小数点,我们不难猜测出,是因为我们往往自定义View的时候,在xml中布局的时候需要写出这个View的全称
所以这里判断是否带了个小数点,判断是否是自定义View。 如果不是自定义的话,走了onCreateView方法后最终也是到createView方法:
protected View onCreateView(String name, AttributeSet attrs)throws ClassNotFoundException {return createView(name, "android.view.", attrs);}
可以发现,与自定义view调用createView方法不同的是,这里多传了个前缀"android.view."
足以看出,我们在XML中声明的自带的控件,会被系统自己加上个类名的前缀以反射出类名创建对象。
我们看一下createView方法:
public final View createView(String name, String prefix, AttributeSet attrs)throws ClassNotFoundException, InflateException {//取得缓存中已经加载过的classConstructor<? extends View> constructor = sConstructorMap.get(name);...if (constructor == null) {// 注意这里,默认prefix则会传进来android.view以构成控件类名全称clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);...constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);//缓存已经加载过的类sConstructorMap.put(name, constructor);}...//反射创建View实例final View view = constructor.newInstance(args);if (view instanceof ViewStub) {// Use the same context when inflating ViewStub later.final ViewStub viewStub = (ViewStub) view;viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}...return view;}
createView方法被我精简了大量代码,因为那些反射代码流程感觉没有太多需要说明的,这里只有一个重点,就是ViewStub登场了
ViewStub源码解析
我们可以看到,如果判断是ViewStub类的话,也只是克隆了一个LayoutInflater实例,并没有做其他操作。
我们知道ViewStub是可以用来懒加载视图的,当我们在xml布局中不需要一开始就显示的布局,我们可以采用ViewStub,当需要用到的时候再inflate进来
ViewStub类的代码也不多,它同样继承了View,所有也可以有View的特性,可以被其他父布局添加成一个子View
ViewStub类定义的一些重点源码如下:
public final class ViewStub extends View {private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {...setVisibility(GONE);setWillNotDraw(true);}public void setLayoutInflater(LayoutInflater inflater) {mInflater = inflater;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(0, 0);}@Overridepublic void draw(Canvas canvas) {}@Overrideprotected void dispatchDraw(Canvas canvas) {}public void setVisibility(int visibility) {//mInflatedViewRef不为空则说明加载过了,直接调用view自己的setVisibilityif (mInflatedViewRef != null) {View view = mInflatedViewRef.get();if (view != null) {view.setVisibility(visibility);} else {throw new IllegalStateException("setVisibility called on un-referenced view");}} else {super.setVisibility(visibility);//可见时装载布局if (visibility == VISIBLE || visibility == INVISIBLE) {inflate();}}}public View inflate() {final ViewParent viewParent = getParent();if (viewParent != null && viewParent instanceof ViewGroup) {if (mLayoutResource != 0) {final ViewGroup parent = (ViewGroup) viewParent;final View view = inflateViewNoAdd(parent);replaceSelfWithView(view, parent);mInflatedViewRef = new WeakReference<>(view);if (mInflateListener != null) {mInflateListener.onInflate(this, view);}return view;} else {throw new IllegalArgumentException("ViewStub must have a valid layoutResource");}} else {throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");}}//替换坑位private void replaceSelfWithView(View view, ViewGroup parent) {final int index = parent.indexOfChild(this);parent.removeViewInLayout(this);final ViewGroup.LayoutParams layoutParams = getLayoutParams();if (layoutParams != null) {parent.addView(view, index, layoutParams);} else {parent.addView(view, index);}}//装载资源布局界面private View inflateViewNoAdd(ViewGroup parent) {final LayoutInflater factory;if (mInflater != null) {factory = mInflater;} else {factory = LayoutInflater.from(mContext);}final View view = factory.inflate(mLayoutResource, parent, false);if (mInflatedId != NO_ID) {view.setId(mInflatedId);}return view;}
}
ViewStub的代码很少,我们调了其中最重要的方法,我们可以知道,在创建的时候为ViewStub克隆了一个LayoutInflater实例赋值给了mInflater
我们发现在构建的时候,ViewStub自己便设置了setVisibility(GONE);和setWillNotDraw(true);
然后还在onMeasure里面传了宽高都是0的数值,并且draw和dispathDraw都是空方法。
这一切足以证明,ViewStub像是一个空袋子,与世无争什么都不干,就只在布局中占着个位置。
我们知道,我们要显示ViewStub内容的时候可以用inflate方法或者是setVisibility方法
我们看他的inflate方法流程,它会先调用inflateViewNoAdd方法来把指定的资源布局加载进来,然后用replaceSelfWithView方法来把自己从原先的父布局中的坑位中替换出来,给这个新加载的布局替换进去。
然后setVisibility方法其实也只是做了个判断,如果未装载,则在VISIBLE or INVISIBLE时调用inflate方法。
由此我们可以得知,原来ViewStub的懒加载原理是这样的,先不加载自己指定的xml到布局中,而是在布局中占了个坑位,没宽高也什么都不显示。
当需要加载的时候再加载布局资源进来,替换自己占的坑位
结束语
相信你在读完本篇后,一定会LayoutInfalter的装载过程有了一定的了解。
并且对布局优化的三个标签(include、merge、ViewStub)的原理理解更加深入。
Android开发知识(二十二)LayoutInflater装载xml布局过程的源码解析相关推荐
- Unity3D研究院之在Unity中打开第三方数据库配合Android开发(三十二)
http://www.xuanyusong.com/archives/831 http://www.xuanyusong.com/archives/1454 如果大家对Unity中如何使用数据库还不是 ...
- Android开发笔记(十二)测量尺寸与下拉刷新
尺寸测量的配置 控件宽和高的设置方式 大家知道,自定义视图的目的就是要在屏幕上显示期望的图案,那在绘制图案之前,我们得先知道这个图案的尺寸(如宽多少高多少). 一般在xml中给控件的宽和高有三种赋值方 ...
- AndroidTv Home界面实现原理(二)——Leanback 库的主页卡位缩放动画源码解析
本篇文章已授权微信公众号 dasu_Android(大苏)独家发布 先看个效果图: 上一篇中,我们留了问题,在 Tv Home 界面这种很常见聚焦卡位放大动画效果,我们这一篇就来看看 Leanback ...
- Android开发系列(十二) QQ联系人列表升级版——ListView和ScrollView高阶使用方法...
今天继续进行QQ界面的开发工作.前一段时间讲过ExpandableListView的使用并且设置了一个比较简单的具有子菜单效果的联系人列表,本节添加进ScrollView控件,对QQ2013版的联系人 ...
- Android开发(三十二)——延时
模拟延时 private class GetDataTask extends AsyncTask<Void, Void, String[]> {@Overrideprotected Str ...
- Android开发知识(十)快速接入高德地图SDK(地图+定位+标记+路线规划+搜索)
文章目录 申请接入流程 显示高德地图 显示定位 Marker 显示地图标记 Route 路线规划 Search 搜索 申请接入流程 1.首先到 [ 高德地图API官网] 申请注册帐号 2.进入控制台, ...
- Android 音视频深入 十五 FFmpeg 推流mp4文件(附源码下载)
源码地址 https://github.com/979451341/Rtmp 1.配置RTMP服务器 这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄 MAC搭建RTMP服务器 ...
- 基于Android开发的音乐播放器小程序带后端(附带学习源码)
实现一个简单的播放器,要求功能有:播放.暂停.停止.退出功能,按停止键会重置封面转角,进度条和播放按钮:按退出键将停止播放并退出程序. 后台播放功能,按手机的返回键和home键都不会停止播放,而是转入 ...
- 并发编程(十六)——java7 深入并发包 ConcurrentHashMap 源码解析
以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...
最新文章
- R:关系型数据库管理
- 算法导论之单源最短路径
- Android开发之百度地图经纬度转换地址(以及获取详细地址的方法自测成功)
- 通过select选项动态异步加载内容
- apache点NET环境
- 读《人月神话》的感想(一)——关于组织结构沟通能力优劣的量化
- VMware ESXi 6.5之前 缺少驱动处理方式
- STM32 USB主机通信连接中断过程
- Makfile 应用进阶实例
- 【交互设计】什么是微交互
- 【QT】将指定ip添加到凭据管理器
- linux 环境变量复制,LINUX系统环境变量PATH ,cp命令 ,mv命令,文档查看cat/more/less/head/tail...
- 苹果自带高德地图搜索周边功能
- 什么是VLAN?VXLAN?以及VLAN和VXLAN的区别?
- 脑洞全开YY无罪-益智类游戏“脑力大战”引发思考
- justify-content属性无效
- ESP32的AP模式使用
- html5的交互式微课,交互式微课这样制作更轻松
- 通过 WiFi 信标进行基于边缘的被动人群监控
- 【渗透测试】SolidState靶机渗透练习_rbash逃逸+4555端口james服务漏洞
热门文章
- 分贝,毫瓦分贝与瓦特之间的关系
- mac mongodb : 715: /data/db/WiredTiger.turtle: handle-open: open: Permission denied
- 中断源、中断向量、矢量中断、中断向量表
- 维基解密再爆料:CIA 2008年就开始监控iPhone了
- 小旋风万能蜘蛛池x9.02开心版/站长必备SEO/永久使用/带教程
- 人口密度修正后的全球疫情热力图,看各国疫情严重程度
- MATLAB傅里叶级数分解极其图像
- JVM/JDK/JRE/IDE—区别
- python常用可视化技巧
- linux /sys目录下的各个子目录说明