android LayoutInflater用法和源码解析

转载请说明来自:http://blog.csdn.net/super_kingking/article/details/51983011

我们在开发过程中都会用到加载xml布局文件,主要常用的俩中情况是 :1.在activity的setContentView(),2.用LayoutInflater解析xml文件,主要使用是在listView,scrollerView,动态添加布局文件等等使用。
今天主要来看一下LayoutInflater的工作原理,并从源码角度解读LayoutInflater的工作机制。

1.获取LayoutInflater的方法。
LayoutInflater的获取有方式有俩种。

1、LayoutInflater inflater = LayoutInflater.from(this);
2、LayoutInflater inflater2 = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

其实这俩种写法是一样的,我们来查看一下LayoutInflater.from(this)源码:

public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
.............................................
        return LayoutInflater;
    }

通过源码我们可以看出,第一种方法是对第二种方法的封装,使用更多方便。

2.LayoutInflater的基本用法
解析xml布局常用的三种方式:
1、inflate(layoutId, null )、
2、inflate(layoutId, root, false)
3、inflate(layoutId, root, true )

下面通过demo分析三者之间的区别,在MainActivity的布局文件activity_main.xml是:

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

</LinearLayout>

分别设置俩个子xml布局文.1、button_no_layout.xml:

<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/btn_cell"
    android:layout_width="120dp"
    android:layout_height="120dp" />

2、button_with_layout.layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_cell"
        android:layout_width="120dp"
        android:layout_height="120dp" />
</LinearLayout>

将button_no_layout.xml和button_with_layout.xml依次添加到activity_main.xml的linearlayout中。下面编辑MainActivity.java代码。

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

        LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.button_with_layout, null);
        linear_content.addView(view);
}

运行显示效果:
这里写图片描述

现在将button_with_layout.xml换掉,更换button_no_layout.xml,运行查看结果:
这里写图片描述

结论:button外面没有包裹layout的xml文件,并没有正常显示

现在调用第二个方法inflate(layoutId, root, false)方法,代码如下:
1、inflater.inflate(R.layout.button_with_layout, linear_content, false);

LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.button_with_layout, linear_content, false);
        linear_content.addView(view);

2、inflater.inflate(R.layout.button_no_layout, linear_content, false)

    LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.button_no_layout, linear_content, false);
        linear_content.addView(view);

俩者显示结果是一样的:

这里写图片描述

结论:均能正常显示button的大小

现在调用第三个方法inflate(layoutId, root, true),代码如下:
1、inflater.inflate(R.layout.button_with_layout, linear_content, true);

    LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.button_with_layout, linear_content, true);
        linear_content.addView(view);

2、inflater.inflate(R.layout.button_no_layout, linear_content, true)

LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.button_no_layout, linear_content, true);
        linear_content.addView(view);
运行崩溃:java.lang.IllegalStateException: The specified child already has a parent.
 You must call removeView() on the child's parent first

运行结果总结:
1.调用第一种方法:inflate(layoutId, null )时,解析并添加button_no_layout.xml布局时,button并没有按照设置的宽高120dp显示。而button_with_layout.xml正常显示了。
2.调用第二种方法时,二者均正常显示。
3、调用第三种方法时,直接无法运行崩溃掉了。

下面让我们从源码的角度解释:

首先进入inflate(layoutId, null )源码,

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            return inflate(resource, root, root != null);
        }

此时,原来该方法内部的本质也是第二,第三种方法,都是调用了,

inflate(int resource,  ViewGroup root, boolean attachToRoot)

只是此时的root==null,attachToRoot等于false.

再往下看:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

        **//xml解析**
            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    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);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

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

            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (Exception e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                                + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            return result;
        }
    }

代码比较长看重点:

    View result = root;
    .......................
       // Temp is the root view that was found in the xml
        //解析xml获取的根(root)View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
        //如果root不为空,并且attachToRoot为false.对应的是.inflate(layoutId, root, false )
                    if (root != null) {
                 ........................
                        //获取root的params参数
                        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
                            temp.setLayoutParams(params);
                        }
                    }
              .............................
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
                    .......................
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
            //如果root不为空,attachToRoot为true.对应的是inflate(layoutId, root, true )
                        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) {
            //如果root等于null,护着attachToRoot为false,对应的是inflate(layoutId, null)
                    //直接返回的是temp
                        result = temp;
                    }
                }
                    }

1、如果root为null,直接返回解析xml的根目录temp.
layoutId布局外面存在包裹的容器layout,则layoutId布局的宽高正常显示,如果不存在layout,则layoutId布局不能正常显示

2、如果root不空,attachToRoot为false,temp设置了root的params属性值。 所以layoutId布局外面无论有没有layout包围,均能够正常显示。

3、如果root不为空,并且attachToRoot为true,root.addview()添加解析的布局文件
我们把linear_content作为root值传递进来,在inflater的时候已经linear_content(root).addView()添加过一次,
在代码中:
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_no_layout,null);
linear_content.addView(view);重复添加,异常直接崩溃。

综上所述可以看出,我们在实际开发过程中:
1、如果xml中包含了Layout容器,则可以添加使用inflate(layoutId, null )、 inflate(layoutId, root, false),

2、如果xml布局文件没有layout容器,布局文件要想正常显示则必须给layoutId布局添加Root,设置root的params…使用inflate(layoutId, root, false) ,inflate(layoutId, root, true ),

3、需要注意的是在使用inflate(layoutId, root, true )时layoutId布局文件会被添加到root当中,所以inflate(xxx)获得的View不可重复添加到root

在activity中setContentView(layoutId),layoutId布局其实调用的也是inflater.inflate(layoutId,root),我看一下源码解释。

在activity代码中:

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow()获取window,window是个抽象类“public abstract class Window ”,phoneWindow是window的实现类,
查看代码分析:

@Override  
   public void setContentView(int layoutResID) {  
       if (mContentParent == null) {  
           installDecor();  
       } else {  
           mContentParent.removeAllViews();  
       }  
       mLayoutInflater.inflate(layoutResID, mContentParent);  
       final Callback cb = getCallback();  
       if (cb != null && !isDestroyed()) {  
           cb.onContentChanged();  
       }  
   }  

可以看出setContentView()内部实现也是通过LayoutInflater,调用inflater.inflate(layoutId,root),root不为空,所以即使 activity_main.xml没有Layout容器,该布局的控件也能正常显示。

总结使用listView的时重写adapter的getView()方法时LayoutInflater的用法

1.
convertView = inflater.inflate(R.layout.button_with_layout, parent, false);
convertView = inflater.inflate(R.layout.button_with_layout,null);
能够正常显示,每个button外面都包裹一个LinearLayout。listView的item是LinearLayout。
item的层级结构
这里写图片描述
2.
convertView = inflater.inflate(R.layout.button_no_layout,null);
button不能正常显示.因为没有Layout容器包裹。无法测量button的大小。
这里写图片描述

3.convertView = inflater.inflate(R.layout.button_no_layout,parent,false);
button正常显示,item是button,层级少。
这里写图片描述

4.convertView = inflater.inflate(R.layout.button_no_layout,parent,true);
convertView = inflater.inflate(R.layout.button_with_layout,parent,true);
崩溃:

addView(View, LayoutParams) is not supported in AdapterView

listView是adapterView的子类。listView不支持addView()就是这个原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值