Android源码解析 --- LayoutParams以及Vi

作者: Android_Jian | 来源:发表于2018-10-21 16:48 被阅读18次

    在分析ViewTree的生成之前,我们先来看下LayoutParams。LayoutParams翻译为“布局参数”,一般情况下,我们在代码中动态设置View的宽高或者Margin的时候会用到它,如下所示:

            TextView tv_test = findViewById(R.id.tv_test);
    
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tv_test.getLayoutParams();
            params.height = height;
            tv_test.setLayoutParams(params);
    

    我们都知道,如果View在布局文件中直接对应的父布局为LinearLayout,那么我们在代码中通过该View的getLayoutParams方法得到的布局参数需要强转成LinearLayout.LayoutParams,why?大家思考过原因吗?我接着再问一句,LayoutParams可以动态设置View的宽高或者Margin,如果我想通过代码来动态设置View的Padding值,需要怎么做呢?你也许会说,这还不简单吗,然后刷刷刷将代码写了出来:

            TextView tv_test = findViewById(R.id.tv_test);
    
            tv_test.setPadding(left,top,right,bottom);
    

    没错,代码的确是这样子的,不知道你有没有想过,为什么LayoutParams可以设置View的宽高或者Margin,却不能设置View的Padding?LayoutParams和View的Padding之间到底有什么关系呢?下面我们就一点一点来解决这些疑惑。

    首先我先简单说下LayoutParams的继承结构,本来应该画张图简洁明了一点贴出来的,无奈笔者画图太丑了,在这里直接语言简单描述下吧。在ViewGroup类中定义了一个内部类LayoutParams,这个LayoutParam类中定义了两个成员变量width、height,分别用来存储View的宽高信息,我们在布局文件中通过layout_width和layout_height属性配置的宽高信息就是存储在这两个成员变量中的。然后呢,ViewGroup类中还定义了一个内部类MarginLayoutParams,MarginLayoutParams继承自ViewGroup中的LayoutParams类,MarginLayoutParams对ViewGroup中的LayoutParams类进行了扩展,新增了leftMargin、topMargin、rightMargin、bottomMargin属性,分别对应View布局文件中的layout_marginLeft、layout_marginTop、layout_marginRight、layout_marginBottom。每个具体的ViewGroup针对各自的职责又分别定制了一个LayoutParams类,继承自ViewGroup中的MarginLayoutParams类,例如LinearLayout类中定义了一个内部类LayoutParams,对layout_weight进行了支持,RelativeLayout类中定义了一个内部类LayoutParams,对layout_centerInParent、layout_alignParentRight等进行了支持。

    首先看下ViewGroup中LayoutParams类中的源码:

        public static class LayoutParams {
            /**
             * Special value for the height or width requested by a View.
             * FILL_PARENT means that the view wants to be as big as its parent,
             * minus the parent's padding, if any. This value is deprecated
             * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
             */
            @SuppressWarnings({"UnusedDeclaration"})
            @Deprecated
            public static final int FILL_PARENT = -1;
    
            /**
             * Special value for the height or width requested by a View.
             * MATCH_PARENT means that the view wants to be as big as its parent,
             * minus the parent's padding, if any. Introduced in API Level 8.
             */
            public static final int MATCH_PARENT = -1;
    
            /**
             * Special value for the height or width requested by a View.
             * WRAP_CONTENT means that the view wants to be just large enough to fit
             * its own internal content, taking its own padding into account.
             */
            public static final int WRAP_CONTENT = -2;
    
            public int width;
    
            public int height;
    
            public LayoutParams(Context c, AttributeSet attrs) {
                TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
                setBaseAttributes(a,
                        R.styleable.ViewGroup_Layout_layout_width,
                        R.styleable.ViewGroup_Layout_layout_height);
                a.recycle();
            }
    
            public LayoutParams(int width, int height) {
                this.width = width;
                this.height = height;
            }
    
            protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
                width = a.getLayoutDimension(widthAttr, "layout_width");
                height = a.getLayoutDimension(heightAttr, "layout_height");
            }
    

    接着看下ViewGroup中的MarginLayoutParams源码:

        public static class MarginLayoutParams extends ViewGroup.LayoutParams {
            /**
             * The left margin in pixels of the child. Margin values should be positive.
             * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
             * to this field.
             */
            @ViewDebug.ExportedProperty(category = "layout")
            public int leftMargin;
    
            /**
             * The top margin in pixels of the child. Margin values should be positive.
             * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
             * to this field.
             */
            @ViewDebug.ExportedProperty(category = "layout")
            public int topMargin;
    
            /**
             * The right margin in pixels of the child. Margin values should be positive.
             * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
             * to this field.
             */
            @ViewDebug.ExportedProperty(category = "layout")
            public int rightMargin;
    
            /**
             * The bottom margin in pixels of the child. Margin values should be positive.
             * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
             * to this field.
             */
            @ViewDebug.ExportedProperty(category = "layout")
            public int bottomMargin;
    
            /**
             * Creates a new set of layout parameters. The values are extracted from
             * the supplied attributes set and context.
             *
             * @param c the application environment
             * @param attrs the set of attributes from which to extract the layout
             *              parameters' values
             */
            public MarginLayoutParams(Context c, AttributeSet attrs) {
                super();
    
                TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
                setBaseAttributes(a,
                        R.styleable.ViewGroup_MarginLayout_layout_width,
                        R.styleable.ViewGroup_MarginLayout_layout_height);
    
                int margin = a.getDimensionPixelSize(
                        com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
                if (margin >= 0) {
                    leftMargin = margin;
                    topMargin = margin;
                    rightMargin= margin;
                    bottomMargin = margin;
                } else {
                    int horizontalMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
                    int verticalMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);
    
                    if (horizontalMargin >= 0) {
                        leftMargin = horizontalMargin;
                        rightMargin = horizontalMargin;
                    } else {
                        leftMargin = a.getDimensionPixelSize(
                                R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                                UNDEFINED_MARGIN);
                        if (leftMargin == UNDEFINED_MARGIN) {
                            mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                            leftMargin = DEFAULT_MARGIN_RESOLVED;
                        }
                        rightMargin = a.getDimensionPixelSize(
                                R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                                UNDEFINED_MARGIN);
                        if (rightMargin == UNDEFINED_MARGIN) {
                            mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                            rightMargin = DEFAULT_MARGIN_RESOLVED;
                        }
                    }
    
                    startMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                            DEFAULT_MARGIN_RELATIVE);
                    endMargin = a.getDimensionPixelSize(
                            R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                            DEFAULT_MARGIN_RELATIVE);
    
                    if (verticalMargin >= 0) {
                        topMargin = verticalMargin;
                        bottomMargin = verticalMargin;
                    } else {
                        topMargin = a.getDimensionPixelSize(
                                R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                                DEFAULT_MARGIN_RESOLVED);
                        bottomMargin = a.getDimensionPixelSize(
                                R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                                DEFAULT_MARGIN_RESOLVED);
                    }
    
                    if (isMarginRelative()) {
                       mMarginFlags |= NEED_RESOLUTION_MASK;
                    }
                }
    
                final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
                final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
                if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
                    mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
                }
    
                // Layout direction is LTR by default
                mMarginFlags |= LAYOUT_DIRECTION_LTR;
    
                a.recycle();
            }
    

    在这里我们简单分析下MarginLayoutParams两个参数的构造方法,可以看到构造方法中首先调用到setBaseAttributes方法,对width和height进行处理,然后调用a.getDimensionPixelSize方法,传递的第一个参数为com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin,简单说就是获取布局文件中View对应的layout_margin的值,如果我们的View在布局文件中设置了layout_margin的话,获取到的值应该大于等于0,如果我们的View在布局文件中没有设置layout_margin,则默认为-1。通过后续代码可以看出,如果我们在布局文件中设置了layout_margin,则以该margin值为准,否则分别获取到R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal和R.styleable.ViewGroup_MarginLayout_layout_marginVertical,也就是View在布局文件中对应的layout_marginHorizontal、layout_marginVertical,如果在布局文件中设置了layout_marginHorizontal或者layout_marginVertical的话,则以该值为准,否则分别获取到对应方向的margin值。一句话来说就是layout_margin的权限>layout_marginHorizontal或者layout_marginVertical>对应方向的margin。到底是不是这个样子呢?大家可以自己验证下,绝对是这个样子哈哈。

    接着我们看下LinearLayout中的LayoutParams:

        public static class LayoutParams extends ViewGroup.MarginLayoutParams {
            /**
             * Indicates how much of the extra space in the LinearLayout will be
             * allocated to the view associated with these LayoutParams. Specify
             * 0 if the view should not be stretched. Otherwise the extra pixels
             * will be pro-rated among all views whose weight is greater than 0.
             */
            @ViewDebug.ExportedProperty(category = "layout")
            public float weight;
    
            /**
             * Gravity for the view associated with these LayoutParams.
             *
             * @see android.view.Gravity
             */
            @ViewDebug.ExportedProperty(category = "layout", mapping = {
                @ViewDebug.IntToString(from =  -1,                       to = "NONE"),
                @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,        to = "NONE"),
                @ViewDebug.IntToString(from = Gravity.TOP,               to = "TOP"),
                @ViewDebug.IntToString(from = Gravity.BOTTOM,            to = "BOTTOM"),
                @ViewDebug.IntToString(from = Gravity.LEFT,              to = "LEFT"),
                @ViewDebug.IntToString(from = Gravity.RIGHT,             to = "RIGHT"),
                @ViewDebug.IntToString(from = Gravity.START,             to = "START"),
                @ViewDebug.IntToString(from = Gravity.END,               to = "END"),
                @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL,   to = "CENTER_VERTICAL"),
                @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL,     to = "FILL_VERTICAL"),
                @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
                @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL,   to = "FILL_HORIZONTAL"),
                @ViewDebug.IntToString(from = Gravity.CENTER,            to = "CENTER"),
                @ViewDebug.IntToString(from = Gravity.FILL,              to = "FILL")
            })
            public int gravity = -1;
    
            /**
             * {@inheritDoc}
             */
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                TypedArray a =
                        c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
    
                weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
                gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
    
                a.recycle();
            }
    

    最后我们再看下RelativeLayout中的LayoutParams:

         public static class LayoutParams extends ViewGroup.MarginLayoutParams {
            @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
                @ViewDebug.IntToString(from = ABOVE,               to = "above"),
                @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),
                @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),
                @ViewDebug.IntToString(from = ALIGN_LEFT,          to = "alignLeft"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,   to = "alignParentLeft"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,  to = "alignParentRight"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,    to = "alignParentTop"),
                @ViewDebug.IntToString(from = ALIGN_RIGHT,         to = "alignRight"),
                @ViewDebug.IntToString(from = ALIGN_TOP,           to = "alignTop"),
                @ViewDebug.IntToString(from = BELOW,               to = "below"),
                @ViewDebug.IntToString(from = CENTER_HORIZONTAL,   to = "centerHorizontal"),
                @ViewDebug.IntToString(from = CENTER_IN_PARENT,    to = "center"),
                @ViewDebug.IntToString(from = CENTER_VERTICAL,     to = "centerVertical"),
                @ViewDebug.IntToString(from = LEFT_OF,             to = "leftOf"),
                @ViewDebug.IntToString(from = RIGHT_OF,            to = "rightOf"),
                @ViewDebug.IntToString(from = ALIGN_START,         to = "alignStart"),
                @ViewDebug.IntToString(from = ALIGN_END,           to = "alignEnd"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_START,  to = "alignParentStart"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_END,    to = "alignParentEnd"),
                @ViewDebug.IntToString(from = START_OF,            to = "startOf"),
                @ViewDebug.IntToString(from = END_OF,              to = "endOf")
            }, mapping = {
                @ViewDebug.IntToString(from = TRUE, to = "true"),
                @ViewDebug.IntToString(from = 0,    to = "false/NO_ID")
            })
    
            private int[] mRules = new int[VERB_COUNT];
    
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
    
                TypedArray a = c.obtainStyledAttributes(attrs,
                        com.android.internal.R.styleable.RelativeLayout_Layout);
    
                final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
                mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
                        !c.getApplicationInfo().hasRtlSupport());
    
                final int[] rules = mRules;
                //noinspection MismatchedReadAndWriteOfArray
                final int[] initialRules = mInitialRules;
    
                final int N = a.getIndexCount();
                for (int i = 0; i < N; i++) {
                    int attr = a.getIndex(i);
                    switch (attr) {
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
                            alignWithParent = a.getBoolean(attr, false);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
                            rules[LEFT_OF] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
                            rules[RIGHT_OF] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
                            rules[ABOVE] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
                            rules[BELOW] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
                            rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
                            rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
                            rules[ALIGN_TOP] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
                            rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
                            rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
                            rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
                            rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
                            rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
                            rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
                            rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
                            rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
                            rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
                           break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:
                            rules[START_OF] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:
                            rules[END_OF] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:
                            rules[ALIGN_START] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:
                            rules[ALIGN_END] = a.getResourceId(attr, 0);
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:
                            rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                        case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:
                            rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;
                            break;
                    }
                }
                mRulesChanged = true;
                System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);
    
                a.recycle();
            }
    

    可以看到RelativeLayout.LayoutParams中将特有属性值存放到了mRules数组中,这点略有不同,应该是考虑到特有属性数量过多的原因吧。

    好了,我们接着从源码的角度分析下ViewTree的生成。通常,布局文件的加载我们这么写:

        View view = LayoutInflater.from(this).inflate(R.layout.layout_test,null);
    

    首先调用LayoutInflater.from(this),获取到LayoutInflater对象,本质为PhoneLayoutInflater实例,具体怎么获取的,在这里我就不赘述了,大家感兴趣的可以翻看下源码。好了我们接着看下inflate方法,跟进去:

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

    接着跟:

        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) + ")");
            }
    
            final XmlResourceParser parser = res.getLayout(resource);
            try {
                //重点
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
        }
    

    接着跟:

        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();
    
                    //判断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);
                    } else {
                        //1.创建根view
                        // 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");
                        }
    
                        //2.加载根view下的所有子view,构建ViewTree
                        // Inflate all children under temp against its context.
                        rInflateChildren(parser, temp, attrs, true);
    
                        if (DEBUG) {
                            System.out.println("-----> done inflating children");
                        }
    
                        //这里由于我们传进来的root为null,attachToRoot为false,跳过该语句块
                        if (root != null && attachToRoot) {
                            root.addView(temp, params);
                        }
    
                        //将布局文件根view赋值给result
                        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(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);
                }
                //最后将布局文件对应的根view return掉
                return result;
            }
        }
    

    我们首先来到第一步,看下根view是怎么创建的,跟进去createViewFromTag方法:

        private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
            return createViewFromTag(parent, name, context, attrs, false);
        }
    
       ...
    
        //最终调用到createView方法
        public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
           
            Class<? extends View> clazz = null;
    
            try {
               
                if (constructor == null) {
                    // 通过类加载器加载对应的class
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
    
                    constructor = clazz.getConstructor(mConstructorSignature);
                    constructor.setAccessible(true);
                    sConstructorMap.put(name, constructor);
                } else {
                   ...
                }
    
                //通过反射创建view对象
                final View view = constructor.newInstance(args);
               
                return view;
            } 
            
            ...
        }
    

    接着我们来到 2 处看下rInflateChildren方法,这才是ViewTree生成的关键:

        final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
                boolean finishInflate) throws XmlPullParserException, IOException {
            rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
        }
    

    接着跟进去:

        void rInflate(XmlPullParser parser, View parent, Context context,
                AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
            final int depth = parser.getDepth();
            int type;
            boolean pendingRequestFocus = false;
    
            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)) {
                    pendingRequestFocus = true;
                    consumeChildElements(parser);
                } 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 {
       
                    //重点
                    //1.调用createViewFromTag方法,最终通过反射创建子view对象
                    final View view = createViewFromTag(parent, name, context, attrs);
                    //2.parent为直接包裹子view的ViewGroup,例如LinearLayout、RelativeLayout等,将parent向上转型为ViewGroup
                    final ViewGroup viewGroup = (ViewGroup) parent;
                    //3.调用viewGroup的generateLayoutParams方法,获取到对应的LayoutParams对象
                    final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                    //4.递归调用rInflateChildren方法,若当前子view同为ViewGroup,为其添加子View
                    rInflateChildren(parser, view, attrs, true);
                    //5.将子View添加到当前ViewGroup中
                    viewGroup.addView(view, params);
                }
            }
    
            if (pendingRequestFocus) {
                parent.restoreDefaultFocus();
            }
    
            if (finishInflate) {
                parent.onFinishInflate();
            }
        }
    

    上述代码中的重点部分我已经做了相应的标注,假设我们当前的ViewGroup为LinearLayout,1处子view对象创建完毕后代码执行到2处,将LinearLayout向上转型为ViewGroup,接着3处调用到viewGroup.generateLayoutParams方法,实质调用到LinearLayout的generateLayoutParams方法,我们跟进去看下:

        #LinearLayout
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LinearLayout.LayoutParams(getContext(), attrs);
        }
    

    可以看到LinearLayout的generateLayoutParams方法中创建了LinearLayout对应的LayoutParams对象。同样我们可以分析出,每一个View对象的创建都伴随着一个具体LayoutParams对象的创建。
    最后我们看下5处,调用viewGroup.addView方法,将子View添加到当前ViewGroup中。由于在这里我们的viewGroup为LinearLayout,所以调用到LinearLayout的addView方法,我们跟进去看下:

        #ViewGroup
        @Override
        public void addView(View child, LayoutParams params) {
            addView(child, -1, params);
        }
    

    接着跟进去:

        public void addView(View child, int index, LayoutParams params) {
            if (DBG) {
                System.out.println(this + " addView");
            }
    
            if (child == null) {
                throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
            }
    
            // addViewInner() will call child.requestLayout() when setting the new LayoutParams
            // therefore, we call requestLayout() on ourselves before, so that the child's request
            // will be blocked at our level
            requestLayout();
            invalidate(true);
            //重点
            addViewInner(child, index, params, false);
        }
    

    可以看到上述方法最终调用到addViewInner方法,我们跟进去看下:

        private void addViewInner(View child, int index, LayoutParams params,
                boolean preventRequestLayout) {
    
            if (mTransition != null) {
                // Don't prevent other add transitions from completing, but cancel remove
                // transitions to let them complete the process before we add to the container
                mTransition.cancel(LayoutTransition.DISAPPEARING);
            }
            //若child.getParent() != null,直接抛出异常
            if (child.getParent() != null) {
                throw new IllegalStateException("The specified child already has a parent. " +
                        "You must call removeView() on the child's parent first.");
            }
    
            if (mTransition != null) {
                mTransition.addChild(this, child);
            }
            //检查 params是否为null,若params为null则调用generateLayoutParams方法创建具体LayoutParams对象
            if (!checkLayoutParams(params)) {
                params = generateLayoutParams(params);
            }
    
            //在这里我们传进来的preventRequestLayout为false,执行else语句
            if (preventRequestLayout) {
                child.mLayoutParams = params;
            } else {
                 //1.调用当前子View的setLayoutParams方法,设置LayoutParams
                child.setLayoutParams(params);
            }
    
            //mChildrenCount为ViewGroup中的一个成员变量,用来记录当前ViewGroup中子View的个数。在这里我们传入的index为-1
            if (index < 0) {
                index = mChildrenCount;
            }
    
            //2.重点,将子View添加到mChildren数组中,
            //此时当前ViewGroup持有子View对象的引用,ViewTree中由ViewGroup指向子View构建完毕
            addInArray(child, index);
    
            //在这里传入的preventRequestLayout为false,执行else语句,将当前ViewGroup对象的引用赋值给子View的mParent成员变量,
            //此时子View持有当前ViewGroup的引用,ViewTree中由子View指向ViewGroup构建完毕
            if (preventRequestLayout) {
                child.assignParent(this);
            } else {
                child.mParent = this;
            }
    
            ...
        }
    

    上述代码中重点部分我已经做了标注,至此ViewTree就构建完毕了。

    相信大家看到这里,对于我文章开始提到的几个问题就可以有自己的理解了,对于问题1,因为ViewTree在构建的过程中,每一个View对象的创建都伴随着一个具体LayoutParams对象的创建,而这个具体的LayoutParams对象正是View对应ViewGroup中的LayoutParams,最后调用view对象的setLayoutParams方法,为当前View对象的mLayoutParams成员变量赋值,而我们在代码中调用到view的getLayoutParams方法,该方法返回的正是mLayoutParams的值,所以自然需要强转成当前View对象对应ViewGroup中的LayoutParams对象啦。对于问题2和3,因为LayoutParams是用来描述子View和对应ViewGroup之间的关系,并没有提供Padding变量,所以通过LayoutParams当然不能设置View的Padding了。LayoutParams和Padding之间的关系可以简单理解为“并列”关系,二者都是作为View对象的成员变量来存在。

    相关文章

      网友评论

      • wan7451:一个对外,一个对内,很容易理解

      本文标题:Android源码解析 --- LayoutParams以及Vi

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