LayoutParams初始化流程

Quibbler 2021-11-1 1115

LayoutParams初始化流程


        在View布局参数LayoutParams一文中对布局参数有了详细的了解。用getLayoutParams()方法获取View的布局参数:

    public ViewGroup.LayoutParams getLayoutParams() {
        return mLayoutParams;
    }

        每个View中的mLayoutParams实例可以通过setLayoutParams(ViewGroup.LayoutParams params)方法设置的:

    public void setLayoutParams(ViewGroup.LayoutParams params) {
        ...
        mLayoutParams = params;
        ...
        requestLayout();
    }

        不需要为View初始化mLayoutParams实例,因为这是View所在容器(ViewGroup)的职责。接下来,就跟随源码,了解View中的LayoutParams初始化。



1、向容器添加View

        最常见的是通过XML的方式进行布局,也可以直接用代码动态向界面添加View。比如用addView(View child)方法:

    TextView textView = new TextView(context);
    ...
    parent.addView(textView);

        当用addView(View child)方法将子View添加到容器的过程中,父容器就会调用构造LayoutParams的相关方法并设置给子View。


1.1、addView()流程图

        先看一下添加View整体的流程。手动画时序图太费时间,直接用SequenceDiagram插件生成了。该插件用法见SequenceDiagram:一键生成时序图一文。


1.2、addView()源码流程

        addView()方法有多个重载,从addView(View child)方法入口开始:

    public void addView(View child) {
        addView(child, -1);
    }

        addView(View child, int index)方法中有两个判断:一是添加到容器中的子View不能为空;二是子View的布局参数如果为空,父容器就会生成一个LayoutParams并作为参数后续设置给子View。

    public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException(
                        "generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

        随后调用addView(View child, int index,params)方法,在该方法里会先于子View更新容器的布局、并使绘图缓存失效。

    public void addView(View child, int index, LayoutParams params) {
        ...
        requestLayout();
        invalidate(true);
        //注意这里不会阻止刷新布局,每当addView都会更新子布局
        addViewInner(child, index, params, false);
    }

        添加子View最终调用的方法是addViewInner(View child, index, params,preventRequestLayout),在该方法里会将addView(View child, int index)方法中传入的LayoutParams通过View的setLayoutParams(params)方法设置给子View,注意preventRequestLayout参数值为false,不会阻止子View请求跟新布局。

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        ...
        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
        if (preventRequestLayout) {
            //直接赋值可能不会使布局参数生效
            child.mLayoutParams = params;
        } else {
            //走这里
            child.setLayoutParams(params);
        }
        ...
    }

        在上面的方法中就用到了本篇开头提到的View中的setLayoutParams(params)方法,容器中子View的布局参数就这样被设置初始化了。

    public void setLayoutParams(ViewGroup.LayoutParams params) {
        if (params == null) {
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        resolveLayoutParams();
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        requestLayout();
    }



2、解析布局文件

        那么通过XML文件解析出来的布局,这里面View的布局参数又是如何初始化的?比直接通过代码addView(View child)添加View,多了一步解析XML的过程,但最后还是需要用addView(View child)方法将解析出来的View添加到容器中。以一条简单的解析xml布局流程为例:


2.1、setContentView()设置布局

        不管设置XML布局用的是AppCompatActivity中的setContentView(int layoutResID)方法:

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

        还是直接用ActivitysetContentView(int layoutResID)方法:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

        最后,都是通过LayoutInflater解析布局。


2.2、LayoutInflater解析布局

        从LayoutInflaterinflate(int resource,ViewGroup root)方法开始解析布局

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

        传入的根视图不为空,就会将解析出的view附加到root中。显然,这里的attachToRoot参数值为true

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        ...
        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();
        }
    }

        解析XML的具体过程先不看,了解大体的解析流程和思路,及解析出来的根View如何处理。子view的解析流程类似。

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            View result = root;
            try {
                advanceToRootNode(parser);
                final String name = parser.getName();
                //<merge/>用法注意事项:根层级必须为ViewGroup,且attach到根视图中
                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 是在 xml 中找到的根视图
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    
                    if (root != null) {
                        // 创建与根匹配的布局参数(如果提供)
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            //如果子view不附加到根视图中,那么为temp设置布局参数.
                            //(如果需要attach到根试图,会在下面通过addView方法添加)
                            temp.setLayoutParams(params);
                        }
                    }
                    
                    //解析所有子View
                    rInflateChildren(parser, temp, attrs, true);
                    
                    // 将解析出来的View附加到root中
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    
                    // 决定是返回传入的root还是xml中解析出来的顶层视图。
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            }
            ...
            return result;
        }
    }

        最终解析出来的XML根布局被添加到哪里去了?其实是被添加到PhoneWindow中的mContentParent容器中。

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

        对PhoneWindow中的installDecor()这个方法还有没有印象?详见DecorView添加到Window中的流程一文的第2节:DecorView的创建



不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com