Android开发经验谈Android开发Android技术知识

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

2018-10-21  本文已影响18人  Android_Jian

在分析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对象的成员变量来存在。

上一篇下一篇

猜你喜欢

热点阅读