LayoutInflater中inflate()方法的学习使用

LayoutInflater中inflate()方法的学习使用

一.LayoutInflater

在这里插入图片描述

​ LayoutInflater实例化一个xml布局加到对应的View对象中。该对象不直接使用。调用getLayoutInflater()或者getSystemService()方法可以获取到该对象的一个典型实例,该实例已经与当前的context建立了联系并且已经被正确配置在了你正在运行的设备中。

二.inflate()方法

在这里插入图片描述
该方法就是根据已有的xml膨胀出一个对应的View层级。

该方法包含如下3个关键参数

resource

ID for an XML layout resource to load (e.g., R.layout.main_page)

需要加载的xml布局的id。

root

Optional view to be the parent of the generated hierarchy (if attachToRoot is true), or else simply an object that provides a set of LayoutParams values for root of the returned hierarchy (if attachToRoot is false.)

当attachToRoot为true时,该root会作为膨胀出的View层的parent父容器。当attachToRoot为false时,它的作用仅仅是为膨胀出的View的根层级来提供一个LayoutParams参数。

attachToRoot

Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view in the XML.

决定被膨胀出来的View层级是否需要被附加到root。如果为false,那root就只限于给xml中的根视图提供正确的LayoutParams参数。

接下来直接开始分析源码。

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) + ")");
    }

    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

看这个方法首先关注一下tryInflatePrecompiled()方法。

private @Nullable
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
    boolean attachToRoot) {
    if (!mUseCompiledView) {
        return null;
    }

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

    // Try to inflate using a precompiled layout.
    String pkg = res.getResourcePackageName(resource);
    String layout = res.getResourceEntryName(resource);

    try {
        Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
        Method inflater = clazz.getMethod(layout, Context.class, int.class);
        View view = (View) inflater.invoke(null, mContext, resource);

        if (view != null && root != null) {
            // We were able to use the precompiled inflater, but now we need to do some work to
            // attach the view to the root correctly.
            XmlResourceParser parser = res.getLayout(resource);
            try {
                AttributeSet attrs = Xml.asAttributeSet(parser);
                advanceToRootNode(parser);
                ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                if (attachToRoot) {
                    root.addView(view, params);
                } else {
                    view.setLayoutParams(params);
                }
            } finally {
                parser.close();
            }
        }

        return view;
    } catch (Throwable e) {
        if (DEBUG) {
            Log.e(TAG, "Failed to use precompiled view", e);
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    return null;
}

首先遇到了mUseCompiledView开关,该参数的意思是是否使用预编译的视图。如果该开关为true的时候会真正执行tryInflatePrecompiled()方法,否则卫语句就返回null出去了。该方法是Android 10(Android Q)中新增的方法,用来根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View,从而减少XmlPullParser解析Xml的时间。它是一个编译优化选项,一般该编译优化没有打开,我们跳过该方法接着往下分析。

mUseCompiledView是false。所以直接就返回了null。接着走到下面的流程中

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

此时进入到try中的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;

        try {
            advanceToRootNode(parser);
            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) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(inflaterContext, attrs)
                    + ": " + 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;
    }
}

首先來一段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);
}

意思是parser解析出来的起始标签是merge标签时,最初传入的root不能为空且attachToRoot必须是true,否则就会报

throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");

满足root不能为空且attachToRoot是true时候进到处理merge的方法中,这里暂不分析这个。往下走。

假如xml布局的起始标签不是merge时就需要创建一个名为temp的View,这个temp就是你最初传入的布局xml中的根层。同时会声明一个LayoutParams类型的变量params。

// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;

此时如果初始传入的root不为空,那么就根据attrs创建出一个LayoutParams赋值给params。接着如果attachToRoot为false时,则该params会直接赋给上面名为temp的View。

if (root != null) {
    ...
    // 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);
    }
}

之后会将temp下面的子View逐个添加到temp上。这里自己感兴趣可以再进去看。

...

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

...

再往下走的话,如果root != null且attachToRoot为true,那就把temp添加到root中且对应此时temp的布局参数与params绑定,最终返回去的视图result其实就是root。如果root == null或者attachToRoot为false,则要返回的的视图result就是temp。

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

三.典型用法

为什么在RecyclerView.Adapter中的onCreateViewHolder()方法inflate布局的时候不能将attachToRoot设置为true?

/**
 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
 *
 * @see #onCreateViewHolder(ViewGroup, int)
 */
@NonNull
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
    try {
        TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
        final VH holder = onCreateViewHolder(parent, viewType);
        if (holder.itemView.getParent() != null) {
            throw new IllegalStateException("ViewHolder views must not be attached when"
                    + " created. Ensure that you are not passing 'true' to the attachToRoot"
                    + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
        }
        holder.mItemViewType = viewType;
        return holder;
    } finally {
        TraceCompat.endSection();
    }
}

假设当你将attachToRoot设置为true的时候,你自己声明的ViewHolder的itemView在膨胀的时候不就被addView在了parent中么,那在上面的代码中holder.itemView.getParent() != null。于是就一定会抛出

ViewHolder views must not be attached when created. Ensure that you are not passing ‘true’ to the attachToRoot parameter of LayoutInflater.inflate(…, boolean attachToRoot)

的异常!!!
看源码,调源码才能真正理解一些原理。纸上得来终觉浅啊!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值