您的位置:首页 > 移动开发 > Android开发

使用layoutinflater的正确姿势

2016-11-04 00:05 453 查看

使用layoutinflater的正确姿势

一开始接触安卓开发的时候,知道layoutinflater是用来将布局文件生成对应的View.那时候还是懵懵懂懂知道需要传递一个layoutId一个parent参数和一个false参数.那时候就这样用,初初还是好好的.直到后来随着进一步学习安卓开发发现layoutinflater的这两个参数是有大大的门道在里面.

然后这一篇博客可以说是我对layoutinflater使用的一个总结.

怎么添加一个View到ViewGroup?

在讨论怎么使用layoutinflater之前,我们先来想这个有趣的问题.对于一个View我们是怎么添加到ViewGroup的?

分别有两种办法(归根到底还是一种而已,事实上以上第一种方法总归还是通过第二种办法实现的)


在编写布局文件的时候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属性


事实上发现,程序并没有报错.而且正常跑起来了…(怎么都不按照剧情发展了?)

最终发现问题的关键点在哪儿.

继承ViewGroup的子类都会重写generateLayoutParams函数.generateLayoutParams函数的作用是把传递进来LayoutParam对象转换成对应的generateLayoutParams对象.例如,在调用LinearLayout#addView函数的时候.





LinearLayout#addView(view,new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT))


如果没把调用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);


然后就是我们的主菜了.

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);
}
}


可以看看这样的用法和以下的用法其实是没区别的.

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}


第一种办法是我们自己调用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);


第一种和第二种用法是没区别,因为root参数为null.你即使给attachToRoot传递什么值都是没意义的.并且返回值是resource资源文件生成的view.

第三种方法和前面两种有小小区别.因为指定了root,那么inflate函数会调用root对象的generateLayoutParams函数生成一个LayoutParam对象并且注入到resource资源文件生成的view.当然返回值和前面两种一样.



第二,如果我只是生成view并且添加到rootVireGroup.你只有唯一的选择了.

LayoutInflater.from(this).inflate(R.layout.activity_main, viewGroup, true);


这时候会把resource资源文件生成的view加入到viewGroup并且返回值会变成viewGroup.

到这里你应该知道使用layoutinflater的正确姿势是怎么样了吧?

那么一个题外话,inflate函数的本着是什么?

inflate函数其实就是封装了一个xml解析器而已,通过解析xml文件解析出节点的名字和属性.然后根据名字找到该View对应的类,调用该类的构造函数(当然还有把解析出来的属性传递给构造函数)生成该view.然后不断递归知道把所有节点解析完.(会根据层次结构生成一个view tree)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 布局