FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

    • LayoutInflater源码分析与应用
      • 一、简述
        • 1、获取LayoutInflater的三种方式
        • 2、创建View的两种方式
      • 二、源码分析
        • 1、View.inflate()最终调用方法探究
        • 2、LayoutInflater的inflate(parser, root, attachToRoot)做了什么?
        • 3、View.inflate()和 LayoutInflater.from(context).inflate()的区别
      • 三、小细节
      • 四、最后
    • ArrayList源码分析
    • LinkedList与Queue源码分析
  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

  • 移动端
  • Android源码
GitLqr
2017-04-23
目录

LayoutInflater源码分析与应用

欢迎关注微信公众号:[FSA全栈行动 👋]

# 一、简述

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的,如:

# 2、创建View的两种方式

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

# 二、源码分析

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

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

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

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

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

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

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

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

嗯??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)方法。

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

好,重点来了,到这步我们可以明白一点,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));

打印结果如下:

同理,将第三个参数传入一个确实存在的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为空,不信?走一个。

接下来理所当然的要让得到的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不为空,但是又有一个问题,看报错。

为什么会报这样的错呢?回看最终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布局:

发现了吧,就一个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,打印结果如下:

# 四、最后

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

#源码分析#LayoutInflater
Android - 自动系统签名
ArrayList源码分析

← Android - 自动系统签名 ArrayList源码分析→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×