View布局参数LayoutParams

Quibbler 2021-10-27 922

View布局参数LayoutParams


        为了计算布局实现动效和LayoutParams密切接触有一段时间,LayoutParams彻底玩明白了。



1、LayoutParams

        LayoutParams直译为布局参数,是定义在ViewGroup中的静态内部类。


1.1、LayoutParams用途

        LayoutParams类的注释清楚的写明此类的用途:LayoutParams是子View用来告诉它们的父布局如何放置自己。

    /**
     * LayoutParams are used by views to tell their parents how they want to be
     * laid out. See
     * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
     * for a list of all child view attributes that this class supports.
     */


1.2、继承关系

        定义在ViewGroup中的LayoutParams类是各种子LayoutParams的基类,只定义View的两个基本属性:宽度和高度。

    /**
     * There are subclasses of LayoutParams for different subclasses of
     * ViewGroup. For example, AbsoluteLayout has its own subclass of
     * LayoutParams which adds an X and Y value.</p>
     *
     * <div class="special reference">
     * <h3>Developer Guides</h3>
     * <p>For more information about creating user interface layouts, read the
     * <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
     * guide.</p></div>
     *
     * @attr ref android.R.styleable#ViewGroup_Layout_layout_height
     * @attr ref android.R.styleable#ViewGroup_Layout_layout_width
     */

        继承抽象类ViewGroup的容器实现类非常多,比如常见的LinearLayoutRelativeLayoutFrameLayout等等。


        有些容器在继承ViewGroup的同时,还会继承ViewGroup.LayoutParams实现自己的LayoutParams。或者直接使用父类的LayoutParams



2、LayoutParams类详解

        通过前面简单的了解,定义在ViewGroup中的LayoutParams基类只有宽高两个基本信息。


2.1、宽 & 高

        ViewGroup中的LayoutParams基类只定义了View的两个基本属性:widthheight,控件的宽和高。

        /**
         * Information about how wide the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT
         * in API Level 8) or WRAP_CONTENT, or an exact size.
         */
        public int width;
        
        /**
         * Information about how tall the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT
         * in API Level 8) or WRAP_CONTENT, or an exact size.
         */
        public int height;
        
        /**
         * 用于动画布局。
         */
        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;

        关于ViewGroup.LayoutParams中对于View宽高的说明如下,值只能是以下三种情况之一:MATCH_PARENTWRAP_CONTENT具体的值。

    /**
     * <p>
     * The base LayoutParams class just describes how big the view wants to be
     * for both width and height. For each dimension, it can specify one of:
     * <ul>
     * <li>FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which
     * means that the view wants to be as big as its parent (minus padding)
     * <li> WRAP_CONTENT, which means that the view wants to be just big enough
     * to enclose its content (plus padding)
     * <li> an exact number
     * </ul>
     */

        有没有很熟悉?这也是XML布局中最常用的两个属性,以layout_前缀加上布局参数的属性。看到以layout_前缀开头的属性,就应该知道这个属性来自LayoutParams

    <TextView
        android:layout_width="match_parent"
        android:layout_height="18dp" />
        
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />


2.2、MATCH_PARENT和WRAP_CONTENT

        ViewGroup.LayoutParams中定义了两个描述MATCH_PARENTWRAP_CONTENT的常量。对于已废弃的FILL_PARENT直接忽略,用MATCH_PARENT替代。

        /**
         * 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;


2.3、LayoutParams的三个构造方法

        ViewGroup.LayoutParams有三个公有构造方法,用来构造ViewGroup.LayoutParams实例。LayoutParams子类也要实现这三个父类构造方法。

        /**
         * Creates a new set of layout parameters. The values are extracted from
         * the supplied attributes set and context. The XML attributes mapped
         * to this set of layout parameters are:
         *
         * <ul>
         *   <li><code>layout_width</code>: the width, either an exact value,
         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
         *   {@link #MATCH_PARENT} in API Level 8)</li>
         *   <li><code>layout_height</code>: the height, either an exact value,
         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
         *   {@link #MATCH_PARENT} in API Level 8)</li>
         * </ul>
         *
         * @param c the application environment
         * @param attrs the set of attributes from which to extract the layout
         *              parameters' values
         */
        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();
        }

        用已知的宽、高构造ViewGroup.LayoutParams对象。LayoutParams子类中也有类似的构造方法。

        /**
         * Creates a new set of layout parameters with the specified width
         * and height.
         *
         * @param width the width, either {@link #WRAP_CONTENT},
         *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
         *        API Level 8), or a fixed size in pixels
         * @param height the height, either {@link #WRAP_CONTENT},
         *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
         *        API Level 8), or a fixed size in pixels
         */
        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }

        用已有的LayoutParams作为参数构造新的LayoutParams对象,俗称复制构造函数

        /**
         * Copy constructor. Clones the width and height values of the source.
         *
         * @param source The layout params to copy from.
         */
        public LayoutParams(LayoutParams source) {
            this.width = source.width;
            this.height = source.height;
        }

        还有一个无参构造方法,框架内部使用,应用开发者无法使用,了解一下即可。

        /**
         * Used internally by MarginLayoutParams.
         * @hide
         */
        @UnsupportedAppUsage
        LayoutParams() {
        }



3、派生LayoutParams

        不能直接使用ViewGroup.LayoutParams类,往往使用其子类。在第1.2节中就已经提到过,在众多继承ViewGroup的容器类中,也有不少派生LayoutParams类。



3.1、MarginLayoutParams

        绝大部分派生LayoutParams并不是直接继承自ViewGroup.LayoutParams,而是继承自ViewGroup.MarginLayoutParams。给原本只有widthheight的布局参数,增加了四个额外的参数:lefttoprightbottom,表示View上下左右四个方向的空白边距。

    public static class MarginLayoutParams extends ViewGroup.LayoutParams {

        public int leftMargin;

        public int topMargin;

        public int rightMargin;

        public int bottomMargin;

        private int startMargin = DEFAULT_MARGIN_RELATIVE;
        
        private int endMargin = DEFAULT_MARGIN_RELATIVE;
        ... ...
    }

         这也是除了2.1节中提到的两个属性之外,在XML布局中常用到的几个layout_属性:

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        
        android:layout_margin="30dp"
        
        android:layout_marginHorizontal="20dp"
        android:layout_marginVertical="20dp"
        
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp" />

        后来为了更好的支持RTL,带有强烈固定方向含义的leftrightstartend取代。建议不要用含有leftstart的属性,而应该用含有startend的属性,可以更好的支持RTL。

        最后,来看一下ViewGroup.MarginLayoutParams的构造函数,从这里面可以更加清晰了解到前面布局中设置的几个margin属性设置流程:

        如果设置了layout_margin,那么用这个作为四个方向的统一边距值,即便设置了其它四个属性也不会生效。

        如果设置了垂直layout_marginVertical,那么layout_marginToplayout_marginBottom属性将不起作用。

        同样的,如果设置了水平layout_marginHorizontal,那么layout_marginLeftlayout_marginRight属性将不起作用。

        layout_marginStartlayout_marginEnd不受除layout_margin之外的属性影响。

        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);
             
            //如果设置了layout_margin,那么用这个作为四个方向的统一边距值,即便设置了其它四个属性也不会生效。
            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;
                }
            }
            ...
        }


3.2、自定义LayoutParams

        开发者可以继承ViewGroup.LayoutParams抽象类或者ViewGroup.MarginLayoutParams类,实现自己的LayoutParams布局参数。

    public class QuibblerView extends ViewGroup {
    
        ...
        
        public static class LayoutParams extends ViewGroup.MarginLayoutParams {
            int front;
            int behind;
            
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.QuibblerView_Layout);
                front = a.getLayoutDimension(R.styleable.QuibblerView_Layout_layout_front, "layout_front");
                behind = a.getLayoutDimension(R.styleable.QuibblerView_Layout_layout_behind, "layout_behind");
                a.recycle();
            }
            
            public LayoutParams(int width, int height) {
                super(width, height);
            }
            
            public LayoutParams(ViewGroup.LayoutParams source) {
                super(source);
            }
        }
        
    }

        再或者省点事,继承现有容器的LayoutParams,只需新增属性方法实现功能。比如直接继承LinearLayout中的LinearLayout.LayoutParams,在很多继承自LinearLayout的容器中也常常看到这样的做法:直接使用父容器的LayoutParams,或者继承父容器的LayoutParams

    public static class LayoutParams extends LinearLayout.LayoutParams {
    
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }
        
        public LayoutParams(int width, int height) {
            super(width, height);
        }
        
        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }


        为了让自定义布局参数更方便些,再自定义一些xml属性。关于自定义</declare-styleable>属性详见《自定义View属性》一文。

    <declare-styleable name="QuibblerView_Layout">
        <attr name="layout_front" format="dimension" />
        <attr name="layout_behind" format="dimension" />
    </declare-styleable>

        在LayoutParams构造方法中解析设置的xml属性:

        public static class LayoutParams extends ViewGroup.MarginLayoutParams {
            int front;
            int behind;
            
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.QuibblerView_Layout);
                front = a.getLayoutDimension(R.styleable.QuibblerView_Layout_layout_front, "layout_front");
                behind = a.getLayoutDimension(R.styleable.QuibblerView_Layout_layout_behind, "layout_behind");
                a.recycle();
            }
            ...
        }



4、使用LayoutParams

        开发者无需自己构造LayoutParams设置给View,给View设置LayoutParams是该View所在父容器的责任,在随后文章中会详细讲到。


4.1、获取View的布局参数

        通常是直接通过View的getLayoutParams()方法获取View中已有的布局参数:

    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();

        每个View中都定义了一个ViewGroup.LayoutParams类型的属性成员mLayoutParams;

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


4.2、修改布局参数

        然后重新设置一些布局参数,如宽高、margin等。对于ViewGroup.LayoutParams布局参数也只有宽高属性可以设置。

    layoutParams.height = height ;
    layoutParams.width = width ;

        如需设置额外的属性,需要将从View拿到的ViewGroup.LayoutParams类型转换成View所在父布局中LayoutParams的类型或其超类。比如,在LinearLayout容器中的View,其布局参数类型为LinearLayout.LayoutParams。可以进行如下操作:

    LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
    layoutParams.setMargins(left, top, right, bottom);
    layoutParams.bottomMargin = 20;
    layoutParams.gravity = Gravity.END;


4.3、设置View的布局参数

        最后,将设置过的LayoutParams再重新设置给View。有些开发者会遇到不设置不生效的情况,或者不二次设置也能生效。

    view.setLayoutParams(layoutParams);

        先说第一种情况,在setLayoutParams(layoutParams)方法中,调用了requestLayout()刷新重新布局,使新的LayoutParams生效。关于requestLayout()详见View的requestLayout()流程一文。

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

        为什么不调用setLayoutParams(layoutParams)方法,也能使修改后的LayoutParams生效?是因为LayoutParams是在布局阶段之前设置的,比如在onCreate()中进行操作,即便没有再次setLayoutParams(layoutParams),也会在后续的布局阶段生效。



相关资料:

        reference/android/view/ViewGroup.LayoutParams

        reference/android/view/ViewGroup.MarginLayoutParams

        自定义控件知识储备-LayoutParams的那些事

        Android LayoutParams用法解析

        

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