美文网首页
LayoutInflater

LayoutInflater

作者: 还是昵称啊 | 来源:发表于2020-07-23 00:25 被阅读0次

LayoutInflater

LayoutInflatre能将一个xml文件解析成对应的View并构建对应的View的关系结构。使用这个类在需要的时候才解析一个布局文件,来避免一开始就加载xml布局文件造成资源浪费。

1. 使用方法

LayoutInflater inflater = LayoutInflater.from(this);
View inflatedLayout = inflater.inflate(R.layout.inflate_layout, mParent, false);

首先通过静态方法传入一个上下文对象获取一个LayoutInflater实例,然后调用inflate方法来解析这个布局文件。这个方法传入三个参数:

  • resource:要解析的资源文件
  • root:解析出来的View的要添加到哪个容器中,相当于这个资源文件的上一层所表示的View
  • attachToRoot:是否将解析出来的View作为子View添加到上一个参数传入的View容器中

2. 源码分析

2.1

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();
}

先判断是否已经解析过该文件,如果已经解析过直接返回解析出来的View,否则就将资源文件准换成一个XmlResourceParser对象,然后调用inflate方法;

2.2

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

这个方法用于对不同的标签类型来执行不同的解析策略,最终把返回一个解析出来的View.在解析过程中主要由以下几个步骤:

  • 把解析出来的View添加到父View中,并设置LayoutParam

  • 解析完成后调用onFinishInflate方法,通知已经解析完成

  • 返回最终解析出来的最顶层的父View,如果inflate参数传入的root不为空且attachToRoottrue,返回的View就是传入的root,否则就是解析出来的顶层的view

  • 如果inflate方法传入的root不为空attachToRoottrue或不传入,就会把解析出来的View作为root下的一个子View.

    总结

    从上面的流程中看出在inflate阶段就会创建布局文件中的所有View实例,并按照层级关系组织好View之间的布局关系。最后返回一个这个布局文件的根View,通过这个View就可以遍历所有的View

    3. findViewById

    从上面的分析中可以看出在inflate阶段就会实例化布局文件中的所有View, 那通过findViewById是怎么拿到对应的View的。

    public final <T extends View> T findViewById(@IdRes int id) {
        if (id == NO_ID) {
            return null;
        }
        return findViewTraversal(id);
    }
    

    这个方法是在View.java这个类中定义的final方法,这个方法会调用另外一个findViewTraversal方法

    protected <T extends View> T findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return (T) this;
        }
        return null;
    }
    

    这个方法是一个可以被重写的方法,在ViewGourp中重写了这个方法,在调用的时候如果当前的View不是一个ViewGroup,就会执行上面的逻辑,判断传入的id是不是等于自己的id,如果是就返回本View否则就返回null

    如果是一个ViewGroup就执行下面的逻辑

    /**
     * {@hide}
     */
    @Override
    protected <T extends View> T findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return (T) this;
        }
    
        final View[] where = mChildren;
        final int len = mChildrenCount;
    
        for (int i = 0; i < len; i++) {
            View v = where[i];
    
            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                v = v.findViewById(id);
    
                if (v != null) {
                    return (T) v;
                }
            }
        }
    
        return null;
    }
    

    ViewGroup中检查Id是否和自己相等,如果不等会遍历自己的所有子View/ViewGroup,然后依次调用他们的findViewById方法,如果子View是一个View就会判断id是否相等,如果相等就返回这个View,如果是ViewGroup就递归的调用这个方法来找子View中是否存在相同id的View,如果遍历完后也没有就说明这个根节点的View下是没有这个id的View。从这里可以看出通过findViewById这个方法查找某个View,这个View必须要是自己的子View,否则就会找不到,并不是在任意的View上调用findViewById方法来查找任意的View都能找到。

    3. 与inflate相关的几个问题

    3.1 ResyclerView、ListView中的子项设置宽高属性失效

    在使用inflate来加载ItemView子项的布局文件时,如果在inlfate参数中参数的rootnull,那布局文件中设置的宽高可能就会失效。因为当传入的root参数为null时,就不会给这个布局文件的中的rootView设置layoutParams

    代码:

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

    看到这有当rootView不为null,并且attachToRootfalse时才会设置布局文件的layoutParam。否则layoutParam就位nullinflate完成后当RecyclerView创建ViewHolder时发现如果当前ViewLayoutParam为空时就是设置一个默认的LayoutParam

    代码:

    //从ViewAdapter中创建一个ViewHolder
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
    //设置LayoutParam
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
                final LayoutParams rvLayoutParams;
                if (lp == null) {
                    rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                    holder.itemView.setLayoutParams(rvLayoutParams);
                } else if (!checkLayoutParams(lp)) {
                    rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                    holder.itemView.setLayoutParams(rvLayoutParams);
                } else {
                    rvLayoutParams = (LayoutParams) lp;
                }
    
    

    获取创建的ViewHolderLayoutParam,如果为空就生成一个默认的LayoutManager,实现的生成的方法在LayoutManager中定义,在LinearLayoutManager的实现为

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
        } else {
            return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }
    

    可以看到LineaLayoutManager中会生成wrap_content的参数值,也就是所有的ViewHolder的布局宽高都是wrap_content的。

    3.2 RecyclerView inflate第三个参数传true报错

    报错的信息:

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

    inflate源码中可以看出,但传入的不为空且attachToRoottrue时,inflate完成返回的为传入的 Root ,holder = mAdapter.createViewHolder(RecyclerView.this, type)在创建一个ViewHodler时传入的root就是当前的ResyclerView对象,所以,如果inflate时传入true,那么返回的还是RecyclerView对象,这样在LayoutManager在调用下面的方法获取一个View时拿到还是当前的RecyclerView。-

    ---疑问:拿到RecyclerView也是可以获取到里面的ViewHolder的,为什么不可以呢。是不是无法确定哪个ViewHolder是新增的

    @NonNull
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }
    

相关文章

网友评论

      本文标题:LayoutInflater

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