[笔记] View 的绘制原理一 ( LayoutInflater原理分析)

Android LayoutInflater原理分析,带你一步步深入了解View(一)

LayoutInflater 是用来加载布局的, 在 Activity 里一般都是用 setContentView(), 其实它内部也是调用了 LayoutInflater.

###基本用法:

  1. LayoutInflater layoutInflater = LayoutInflater.from(context);
  2. LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

第一种就是第二种的封装.

得到 LayoutInflater 的实例后就可以调用它的 inflate() 来加载布局了.

layoutInflater.inflate(resourceId, root);

  • 第一个参数是要加载的布局 id
  • 第二个参数指给该布局再嵌套一层父布局, 若不需要直接传 null

LayoutInflater 广泛应用于需要动态添加View的时候,比如在 ScrollView 和 ListView中,经常都可以看到 LayoutInflater 的身影。

###LayoutInflater 是如何工作的
无论是使用的哪个 inflate() 方法的重载, 最终会调用下面的代码

 /**
 * Inflate a new view hierarchy from the specified XML node. Throws
 * {@link InflateException} if there is an error.
 * <p>
 * <em><strong>Important</strong></em>   For performance
 * reasons, view inflation relies heavily on pre-processing of XML files
 * that is done at build time. Therefore, it is not currently possible to
 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
 * 
 * @param parser XML dom node containing the description of the view
 *        hierarchy.
 * @param root Optional view to be the parent of the generated hierarchy (if
 *        <em>attachToRoot</em> is true), or else simply an object that
 *        provides a set of LayoutParams values for root of the returned
 *        hierarchy (if <em>attachToRoot</em> is false.)
 * @param 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.
 * @return The root View of the inflated hierarchy. If root was supplied and
 *         attachToRoot is true, this is root; otherwise it is the root of
 *         the inflated XML file.
 */
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 {
            // 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;
    }
}
复制代码

它使用了 Android 提供的 pull 解析方式来解析布局文件.
它调用 createViewFromTag() 方法把节点名和参数传了进去. 它根据节点名创建 View 对象. 内部调用 createView(),
然后使用反射的方式创建出 View 的实例并返回.

这里只是创建出了一个根布局的实例而已,接下来会在第31行调用 rInflate() 方法来循环遍历这个根布局下的子元素

/**
 * Recursive method used to descend down the xml hierarchy and instantiate
 * views, instantiate their children, and then call onFinishInflate().
 * <p>
 * <strong>Note:</strong> Default visibility so the BridgeInflater can
 * override it.
 */
void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();
        
        if (TAG_REQUEST_FOCUS.equals(name)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}  
复制代码

内部同样是 createViewFromTag() 方法来创建 View 的实例,然后递归调用 rInflate() 方法来查找这个 View 下的子元素,每次递归完成后则将这个 View 添加到父布局当中。 这样的话,把整个布局文件都解析完成后就形成了一个完整的 DOM 结构,最终会把最顶层的根布局返回,至此 inflate() 过程全部结束。

inflate(int resource, ViewGroup root, boolean attachToRoot) 该方法的 attachToRoot 是什么意思?

  1. 如果 root 为 null,attachToRoot 将失去作用,设置任何值都没有意义。
  2. 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root。
  3. 如果 root 不为 null,attachToRoot 设为 false,则会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout 属性会自动生效。
  4. 在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。

在 setContentView() 方法中,Android 会自动在布局文件的最外层再嵌套一个 FrameLayout,所以 layout_width 和 layout_height属性才会有效果。那么我们来证实一下

 AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.main_appbar);
 ViewParent parent = appBarLayout.getParent();
 Logger.d(TAG,"parent-->"+parent);
复制代码

打印 AppbarLayout 的父控件

parent-->android.support.v7.widget.ContentFrameLayout{2413023 V.E...... ......I. 0,0-0,0 #1020002 android:id/content}  
复制代码

父布局确实是一个 ContentFrameLayout,而这个FrameLayout就是由系统自动帮我们添加上的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值