一开始接触安卓开发的时候,知道layoutinflater是用来将布局文件生成对应的View.那时候还是懵懵懂懂知道需要传递一个layoutId一个parent参数和一个false参数.那时候就这样用,初初还是好好的.直到后来随着进一步学习安卓开发发现layoutinflater的这两个参数是有大大的门道在里面.
然后这一篇博客可以说是我对layoutinflater使用的一个总结.
怎么添加一个View到ViewGroup?
在讨论怎么使用layoutinflater之前,我们先来想这个有趣的问题.对于一个View我们是怎么添加到ViewGroup的?
分别有两种办法(归根到底还是一种而已,事实上以上第一种方法总归还是通过第二种办法实现的)
- 1
在编写布局文件的时候View作为ViewGroup的子节点
通过调用ViewGroup的addView系列方法添加View
我们关注紫色框圈起来的两个addView方法.这两个方法区别就是是否传递LayoutParam参数.为什么要传递这个参数?为什么又可以不彻底?
很好理解嘛,不传递那么我就默认帮你构造一个就完事了.
看到代码其实如不使用addView(View child)给一个ViewGroup添加View.需要添加的View自带了LayoutParam那么在添加的过程中我就取出来并且拿来使用,如果View是没有附上LayoutParam那么我就帮你构造一个ViewGroup.Layout.
这里要注意一个很严峻的问题.在ViewGroup的代码里面使用generateDefaultLayoutParams函数生成一个ViewGroup.LayoutParam对象.
但是你换成ViewGroup的子类LinearLayout(当然其他子类也可以,这里拿LinearLayout作为讲解).你会发现generateDefaultLayoutParams函数重写了!并且不是生成ViewGroup.LayoutParam对象而是LinearLayout.LayouParam对象了!
这样绕了一圈我到底想表达什么?我是想让你知道.一个View添加到ViewGroup是必须要使用对应的LayoutParam.
可以做一个小测试..给LinearLayout添加一个内部持有ViewGroup.LayoutParam对象的View.
看看LinearLayout的部分代码片段,LinearLayout会把自己包含的子View拿出来.并且拿到子View的LayoutParam强转为LineLayout.LayoutParam并且使用里面相应的属性.
LineLayout.LayoutParam继承ViewGroup.MarginLayoutParams
ViewGroup.MarginLayoutParams继承ViewGroup.LayoutParams
ViewGroup.MarginLayoutPara添加leftMargintopMarginrightMargin,bottomMargin属性
- 1
- 2
- 3
事实上发现,程序并没有报错.而且正常跑起来了…(怎么都不按照剧情发展了?)
最终发现问题的关键点在哪儿.
继承ViewGroup的子类都会重写generateLayoutParams函数.generateLayoutParams函数的作用是把传递进来LayoutParam对象转换成对应的generateLayoutParams对象.例如,在调用LinearLayout#addView函数的时候.
LinearLayout#addView(view,new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT))
- 1
如果没把调用addView时候传递进来的RelativeLayout.LayoutParams对象转换为LinearLayout.LayoutParams,那么在LinearLayout使用这个对象的时候就肯定有问题.当然这个装工作就交给generateLayoutParams函数完成的.
如果addView的时候传递的不是该布局内部的LayoutParams肯定会把一些属性遗弃掉.就像你不可能这样玩吧?
GridLayout.LayoutParams params;
params = new GridLayout.LayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
params.setGravity(Gravity.LEFT);
LinearLayout layout = (LinearLayout) findViewById(R.id.activity_main);
layout.addView(view,params);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
然后就是我们的主菜了.
layoutinflater详解
通过layoutinflater把R.layout.activity_main生成view然后调用setContentView.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = LayoutInflater.from(this).inflate(R.layout.activity_main, (ViewGroup) findViewById(android.R.id.content), false);
setContentView(view);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
可以看看这样的用法和以下的用法其实是没区别的.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第一种办法是我们自己调用layoutinflater把布局生成为View然后调用setContentView加到ContentView里面.第二种办法是setContentView内部调用LayoutInflater生成布局并且在LayoutInflater里面把布局添加到ContentView.
inflate函数的参数
inflate函数功能很简单粗暴,函数根据你传递进来的布局生成一个View.并且返回一个View.
接下来看看inflate函数的三个参数
resource参数
众所周知resource参数就是需要解析的布局文件id.
attachToRoot参数
attachToRoot参数很有意思,对于它的设置会导致两种很微妙的变化.而且layoutinflater的使用其实弄懂attachToRoot参数可以说就已经是掌握了80%了.
第一,attachToRoot参数字面上就已经说明白这参数的作用了.就是使用resource生成view以后是否把该view添加到root参数所指定的ViewGroup当中.
第二,attachToRoot参数决定了返回值到底返回什么.如果attachToRoot为true那么inflate返回值就是root参数所传递的值.如果attachToRoot为false,那么返回值就是resource资源文件生成的view.
这里要说明两种情况,如果root为null怎么办?如果root基本上就已经是忽略attachToRoot参数的值了,直接返回resource资源文件生成的view.
这里是唯一改变resutl的地方,否则就是返回RootViewgroup.
root参数
经过上面这样分析了前面两个参数,也应该知道root参数就是一个ViewGroup而已了.
但是细细看代码你会发现一个有意思的地方,root参数指定resource资源文件生成的view一个加入到哪一个viewGroup.并且inflate函数会使用root参数指定的viewGroup生成LayoutParam参数.
看起来有意思吧!其实结果前面第一部分分析,我们知道用viewGroup生成LayoutParam其实也没多少意义.
layoutinflater的正确姿势
layoutinflater使用核心的一个问题是,我通过resource资源文件生成的view是否要加到rootVireGroup里面.把这个需求确定了,layoutinflater想怎么用就这么用.
第一,如果我只是生成view而已,无需添加到rootVireGroup.你有如下三种选择
LayoutInflater.from(this).inflate(R.layout.activity_main, null, true);
LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);
LayoutInflater.from(this).inflate(R.layout.activity_main, viewGroup, false);
- 1
- 2
- 3
第一种和第二种用法是没区别,因为root参数为null.你即使给attachToRoot传递什么值都是没意义的.并且返回值是resource资源文件生成的view.
第三种方法和前面两种有小小区别.因为指定了root,那么inflate函数会调用root对象的generateLayoutParams函数生成一个LayoutParam对象并且注入到resource资源文件生成的view.当然返回值和前面两种一样.
第二,如果我只是生成view并且添加到rootVireGroup.你只有唯一的选择了.
LayoutInflater.from(this).inflate(R.layout.activity_main, viewGroup, true);
- 1
- 这时候会把resource资源文件生成的view加入到viewGroup并且返回值会变成viewGroup.
到这里你应该知道使用layoutinflater的正确姿势是怎么样了吧?
那么一个题外话,inflate函数的本着是什么?
inflate函数其实就是封装了一个xml解析器而已,通过解析xml文件解析出节点的名字和属性.然后根据名字找到该View对应的类,调用该类的构造函数(当然还有把解析出来的属性传递给构造函数)生成该view.然后不断递归知道把所有节点解析完.(会根据层次结构生成一个view tree)