美文网首页
LayoutInflater中inflate源码分析

LayoutInflater中inflate源码分析

作者: 亦猿非猿 | 来源:发表于2018-11-09 07:51 被阅读1次

对项目中的代码进行优化,删除Layout中过度绘制的代码,无意中发现了一个问题,ListView中的item的高度设置后没有效果,listview算是Android开发中最常用的控件之一,一直都在用,但是却没有遇到过item中设置无效,找到解决办法后,意识到自己对平时调用的方法参数不够理解。

问题重现

原来的的xml布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="48dp">
    
    <ImageView
        android:id="@+id/iv_title"
        android:layout_width="32dp"
        android:layout_height="32dp" 
        android:layout_centerVertical="true"/>

</RelativeLayout>

不管怎样修改RelativeLayout中height中的值,运行后的item高度,一直保持不变,为32dp。

解决办法

  1. 通过内层控件去控制去控制整个item的高度,修改如下

    <ImageView
            android:id="@+id/iv_title"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:layout_centerVertical="true"/>
    
  2. 为RelativeLayout设置最小高度,添加属性

    android:minHeight="48dp"
    
  3. 虽然可以解决该问题,但是总觉得指标不治本,如果遇到xml中嵌套多个控件,那么这种上面两种解法就难以实施了。再继续查找解决办法,原来Adapter中用的是

    LayoutInflater.from(mAct).inflate(layoutId,null);
    

    改为如下则可以解决

    LayoutInflater.from(mAct).inflate(layoutId,parent,false);
    

    为啥参数不同就导致结果不同呢?看下面的详细分析

LayoutInflater.inflate()的分析

简介

Instantiates a layout XML file into its corresponding {@link android.view.View}
* objects.

实例化一个XML布局文件到相应的View对象中

使用

LayoutInflater layoutInflater = LayoutInflater.from(mContext);

It is never used directly. Instead, use
* {@link android.app.Activity#getLayoutInflater()} or
* {@link Context#getSystemService} to retrieve a standard LayoutInflater instance

从注释可以看出,不直接创建对象,而是通过去取回一个实例,看from(mContext)的实现可以发现最终也是通过getSystemService获取

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

inflate()源码分析

image

inflate()有四个重载的方法,第一二个方法把resource解析为XmlResourceParser

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

最后都调用了第四个方法

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

            final Context inflaterContext = mContext;
            //把pareser转化为attributeSet
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            //把父布局赋予result
            View result = root;

            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();
                ···
                
                //判断名称是否为merge
                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
                    // 获取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)
                            // 设置xml的根布局属性
                            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) {
                        //把xml的根布局附上到root中
                        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;
        }
    }

从上面的代码可以看出,先判断是否为merge标签,如是的话,root不能为空或者attachToRoot不能为false,否则会抛出异常。

不为merge标签,获取xml中的根布局,如果root不为空,则生成xml中的属性params,如果attachToRoot为false,则设置到xml的根布局中,此时属性值有效。

如果attachToRoot为true,则把xml中的根布局以及属性附到root中,此时属性值同样有效。

if (root == null || !attachToRoot) {
    result = temp;
}

最后,返回view,只有root不为空且attachToRoot为true,才返回root,否则都返回xml的根布局。

可以知道,listView中的getView是返回子item,为了让属性值有效,应该改为

convertView = LayoutInflater.from(mContext).inflate(R.layout.item_listtest, parent,false);

如果最后的attachToRoot参数为true,那么会发生崩溃,日志如下,因为getView拿到item后,还再执行addView。

Caused by: java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView

如果想在一个界面中,想让rootView中添加一个View,让其属性有效,那么则改为

convertView = LayoutInflater.from(mContext).inflate(R.layout.item_listtest, parent, true);

总结

返回布局

  • 如果root不为null,attachToRoot为true,xml被addView到root中,返回root布局
  • 如果root为null,或者attachToRoot为false,返回xml布局

属性值

  • 如果root为null,属性值无效,因为属性值需要root布局去root.generateLayoutParams(),其他情况,属性值有效

相关文章

网友评论

      本文标题:LayoutInflater中inflate源码分析

      本文链接:https://www.haomeiwen.com/subject/bdrgxqtx.html