RecyclerView中使用LayoutInflater.inflate的流程分析及注意事项

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l460133921/article/details/85097971

在RecyclerView适配器中的onCreateViewHolder函数中一直使用如下方式生成item view,但从没考虑过inflate的具体流程是什么样的,其各个参数具体如何起作用,今天来具体分析一下inflate的代码流程和参数的具体作用,以明确在使用RecyclerView过程中的注意事项。

View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.example, root, false);

先上inflate源码(代码有删减):

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);(1)
        try {
            return inflate(parser, root, attachToRoot);(2)
        } finally {
            parser.close();
        }
    }

注意参数root实际上就是RecyclerView自身,因为RecyclerView本身即是ViewGroup的子类,其传递方法如下:

holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type);

从代码中可以看出有两个关键的步骤:位置(1)处先生成一个资源文件resource的解析器,然后在位置(2)调用另一个inflate函数,其代码如下(代码有删减):

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; //默认返回的View为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(); //获得布局文件的根标签,这里假设是LinearLayout

                if (TAG_MERGE.equals(name)) { //如果根标签是merger
                    //如果root为空或attachToRoot=false,则抛出异常,在实际使用中attachToRoot一般设置为false,因此布局文件的根标签建议不要使用merge
                    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);//根据根标签创建View,这里的标签name假设为LinearLayout (1)

                    ViewGroup.LayoutParams params = null;

                    if (root != null) { //root不为空的情况下
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs); //由root根据布局文件生成LayoutParams (2)
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params); //设置生成View的LayoutParams (3)
                        }
                    }

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

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true); //解析temp View的子控件

                    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;  //设置返回的View为解析出来的布局文件
                    }
                }

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

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

代码中比较关键的函数调用解释如下:
(1):createViewFromTag(root, name, inflaterContext, attrs)根据根标签创建View,这里的标签name假设为LinearLayout
在createViewFromTag中最终是调用LayoutInflater的public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException创建View。在createView中通过反射最终调用到LinearLayout的如下构造函数:

public LinearLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

可以看出如果自定义一个ViewGroup,含有两个参数的构造函数是必须要自己实现的。
(2):params = root.generateLayoutParams(attrs)由root根据布局文件生成LayoutParams
前面说过root实际上就是RecyclerView自已,因此这里调用的是RecyclerView中覆写的generateLayoutParams函数,其代码如下:

public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        if (this.mLayout == null) { //mLayout为空的话抛出异常,可见必须设置RecyclerView的LayoutManager
            throw new IllegalStateException("RecyclerView has no LayoutManager" + this.exceptionLabel());
        } else {
            return this.mLayout.generateLayoutParams(this.getContext(), attrs); //由LayoutManager根据布局文件生成LayoutParams
        }
 }

(3):temp.setLayoutParams(params)设置解析出来布局文件的LayoutParams,这也是我们在布局文件中设置的布局参数真正起作用的地方。可以看出如果root为空且attachToRoot为真,我们在布局中设置的参数是不会起 作用的。

以上就是inflate的执行的关键流程,通过代码分析可以明确RecylerView在其适配器中向inflate传递参数时需要注意以下几点:

  1. root不能为空,否则布局文件中设置的布局参数不会起作用
  2. attach不能设置为true,否则inflate返回的布局为RecylerView,而不是我们希望返回的自定义布局对象
  3. 必须为RecylerView指定LayoutManager,否则会抛出异常

猜你喜欢

转载自blog.csdn.net/l460133921/article/details/85097971
今日推荐