使用layoutinflater的正确姿势

Posted dsliang12

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用layoutinflater的正确姿势相关的知识,希望对你有一定的参考价值。

使用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)

以上是关于使用layoutinflater的正确姿势的主要内容,如果未能解决你的问题,请参考以下文章

使用layoutinflater的正确姿势

使用 Kotlin 更改片段中的按钮背景

setContentView 和 LayoutInflater 有啥区别?

真的知道LayoutInflater的正确用法么?

真的知道LayoutInflater的正确用法么?

谷歌地图在安卓设备上显示为灰色