8

LayoutInflater源码分析与应用 - JackPeng博客

 3 years ago
source link: http://yuanfentiank789.github.io/2017/06/15/layoutinflater/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

LayoutInflater源码分析与应用

作者 CSDN_LQR 关注
2017.04.23 13:21* 字数 1845 阅读 426评论 6喜欢 6

转载自[http://www.jianshu.com/p/de7f651170be]

LayoutInflater直译为 布局填充器,它是用来创建布局视图的,常用inflate()将一个xml布局文件转换成一个View,下面先介绍下获取LayoutInflater的三种方式 和 创建View的两种方式。

1、获取LayoutInflater的三种方式

  1. LayoutInflater inflater = getLayoutInflater(); //调用Activity的getLayoutInflater()
  2. LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  3. LayoutInflater inflater = LayoutInflater.from(context);

其实不管是哪种方式,最后都是通过方式2获取到LayoutInflater的,如:

1240

2、创建View的两种方式

  1. View.inflate();
  2. LayoutInflater.from(context).inflate();

二、源码分析

上面两种创建View的方式都是开发中常用的,那两者有什么关系吗?下面对View.inflate()进行方法调用分析:

1、View.inflate()最终调用方法探究

1)按住Ctrl+鼠标左键查看View.inflate()方法

1240

可以看到View.inflate()就是调用了LayoutInflater.from(context).inflate()。

好,到这一步要明确,不管我们研究哪种方式,实际上都研究方式2,即LayoutInflater.from(context).inflate()。

2)按住Ctrl+鼠标左键查看LayoutInflater.from(context).inflate(resource, root)方法。

1240

嗯?LayoutInflater.from(context).inflate(resource, root)再调用了自己的重载inflate(resource, root, root != null)。

3)按住Ctrl+鼠标左键查看LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null)方法。

1240

嗯??LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null)再再调用了自己的重载inflate(parser, root, attachToRoot)。

4)按住Ctrl+鼠标左键查看LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null).inflate(parser, root, attachToRoot)方法。

这下总算是到头了,不过代码太长,这里就截了一半的图(这不是重点)。

1240

好,重点来了,到这步我们可以明白一点,View.inflate()整个方法调用链如下:

View.inflate() = 
    LayoutInflater.from(context)
        .inflate(resource, root)
        .inflate(resource, root, root != null)
        .inflate(parser, root, attachToRoot)

2、LayoutInflater的inflate(parser, root, attachToRoot)做了什么?

由于代码太长,不方便截图,下面贴出代码中的重点代码:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {

                ...
                省略代码~
                ...

                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

                ...
                省略代码~
                ...

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }

                ...
                省略代码~
                ...

        return result;
    }
}

该inflate方法中有以下四步操作:

  1. 通过使用XmlPullParser parser将xml布局文件转换成视图temp。
  2. 判断ViewGroup root对象是否为null,来决定要不要给temp设置LayoutParams。
  3. 判断boolean attachToRoot是否为true,来决定是否要把temp顺便加到ViewGroup root中。
  4. 最后返回视图temp。

到这里就把创建视图的流程分析完了,接下来是比较 View.inflate()和 LayoutInflater.from(context).inflate()的区别。

3、View.inflate()和 LayoutInflater.from(context).inflate()的区别

1)View.inflate()第三个参数的解析:

开发中常常会对第三个参数(ViewGroup root)传入null吧,通过上面对最终inflate方法的分析,可以知道,如果ViewGroup root取值为null,则得到的视图temp不会被设置LayoutParams。下面做个试验:

View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
ViewGroup.LayoutParams params = itemView.getLayoutParams();
Log.e("CSDN_LQR", "params == null : " + (params == null));

打印结果如下:

1240

同理,将第三个参数传入一个确实存在的ViewGroup时,结果就是视图temp能获取到LayoutParams,有兴趣的可以自己试试。

2)LayoutInflater.from(context).inflate()的优势:

*下面的场景分析将体现出LayoutInflater.from(context).inflate()的灵活性。

如果是在RecyclerView或ListView中使用View.inflate()创建布局视图,又想对创建出来的布局视图进行高度等参数设置时,会有什么瓶颈呢?

下面贴出我之前写过的一段用于瀑布流适配器的代码:

public class MyStaggeredAdapter extends RecyclerView.Adapter<MyStaggeredAdapter.MyViewHolder> {

    private List<String> mData;
    private Random mRandom = new Random();

    public MyStaggeredAdapter(List<String> data) {
        mData = data;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //这里使用的是安卓自带的文本控件布局
        View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        //为实现瀑布流效果,需要对条目高度进行设置(让各个条目的高度不同)
        ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();
        params.height = mRandom.nextInt(200) + 200;
        holder.mTv.setLayoutParams(params);
        holder.mTv.setBackgroundColor(Color.argb(255, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30));
        holder.mTv.setText(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView mTv;

        public MyViewHolder(View itemView) {
            super(itemView);
            mTv = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }

}

经过上面对View.inflate()的第三个参数解析之后,这代码的问题一眼就能看出来了吧,没错,就是ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();这行代码获取到的LayoutParams为空,不信?走一个。

1240

接下来理所当然的要让得到的LayoutParams不为空啦,所以将onCreateViewHolder()的代码修改如下:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //这里使用的是安卓自带的文本控件布局
    View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent);
    return new MyViewHolder(itemView);
}

传入的ViewGroup parent不为null,所以肯定获取的LayoutParams不为空,但是又有一个问题,看报错。

1240

为什么会报这样的错呢?回看最终inflate()的四个步骤:

  1. 通过使用XmlPullParser parser将xml布局文件转换成视图temp。
  2. 判断ViewGroup root对象是否为null,来决定要不要给temp设置LayoutParams。
  3. 判断boolean attachToRoot是否为true,来决定是否要把temp顺便加到ViewGroup root中。
  4. 最后返回视图temp。

步骤2让条目获取的LayoutParams不为空没错,但是步骤3出问题了,当使用View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent)传入parent后,boolean attachToRoot的取值就是为true,所以创建出来的条目会顺便添加到ViewGroup中(这里的ViewGroup就是RecyclerView),而RecyclerView本身就会自动将条目添加到自身,这样就添加了两次,故报错。那为什么attachToRoot的取值是true呢?再看View.inflate()的整个方法调用链:

View.inflate() = 
    LayoutInflater.from(context)
        .inflate(resource, root)
        .inflate(resource, root, root != null)
        .inflate(parser, root, attachToRoot)

boolean attachToRoot的取值取决于root(也就是parent)是否为空,这就是View.inflate()的瓶颈,它没法灵活的指定boolean attachToRoot的取值。这里我就是只是想让创建出来的视图能得到LayoutParams,但不添加到ViewGroup中,这样的要求可以通过LayoutInflater.from(context).inflate()来实现。所以下面将onCreateViewHolder()的代码修改如下:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false);
    ViewGroup.LayoutParams params = itemView.getLayoutParams();
    Log.e("CSDN_LQR", "params == null : " + (params == null));
    return new MyViewHolder(itemView);
}

代码中LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false)传入了parent(即ViewGroup不为null),所以创建出来的视图可以得到LayoutParams,同时又指定attachToRoot的取值为false,即不添加到ViewGroup中。到这里,上面重覆添加子控件的问题就解决了,总结一下吧:

  • View.inflate()第三个参数若不为null,则创建出来的视图一定能获得LayoutParams,反之,不一定。(下面会解释)
  • LayoutInflater.from(context).inflate()可以灵活的指定传入的ViewGroup是否为空来决定创建出来的视图能否获得LayoutParams,同时又可以指定attachToRoot的取值来决定创建出来的视图是否要添加到ViewGroup中。

三、小细节

*上面已经将LayoutInflater的源码分析完毕,现在还有一个小问题,其实跟本文主题没多大关系,当作拓展来看吧。

前面说到,View.inflate()第三个参数若不为null,则创建出来的视图一定能获得LayoutParams,反之,不一定。这话怎么理解?

也就是说,即使View.inflate()第三个参数为null,创建出来的视图也有可能获得LayoutParams咯?是的,说到底,这个LayoutParams的有无,实际取决于条目本身是否有父控件,且看上面用到的simple_list_item_1布局:

1240

发现了吧,就一个TextView,没有父控件,那如果我给它加个父控件,同时使用最开始的方式也能顺利得到LayoutParams呢?代码如下:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = View.inflate(parent.getContext(), R.layout.item_layout, null);
    return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();
    Log.e("CSDN_LQR", "params == null : " + (params == null));
    ...
    控件设置
    ...
}

item_layout的布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <TextView
        android:id="@android:id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:minHeight="?android:attr/listPreferredItemHeightSmall"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
</LinearLayout>

运行,果然可以获得LayoutParams,打印结果如下:

1240

本人也是头次写类分析型的文章,如描述有误,请不吝赐教,同时还请各位看客多担待,指出后本人会尽快修改,谢谢。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK