Suppose you have the following layout file

<?xml version="1.0" encoding="utf-8"? >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="Hello World!" />

</LinearLayout>
Copy the code

When the system loads the layout, it creates a LinearLayout object and a TextView object. Then it calls the LinearLayout.addView() method to save the TextView object. . It will also create LinearLayout LayoutParams object to the layout of the statement to preserve the TextView parameters, such as layout_width and layout_height. That’s why you can declare the layout_width and layout_height properties in the child View of the LinearLayout.

Basic layout parameters

ViewGroup class has two basic layout parameters, ViewGroup. LayoutParams and ViewGroup MarginLayoutParams.

All custom ViewGroup layout parameter classes must inherit directly or indirectly from ViewGroup.LayoutParams because you need to support layout_width and layout_height layout properties.

If you want to make a custom attribute support margin ViewGroup layout parameters, such as layout_marginLeft, so a custom layout parameters of ViewGroup class needs to directly or indirectly inherits from ViewGroup. MarginLayoutParams, Because it has the ability to parse the margin property.

Customize layout parameters

Suppose you now have a custom ViewGroup called a CornerLayout. The CornerLayout determines which corner the child View will be placed based on its layou_corner layout property. For example, now you have the following layout

<?xml version="1.0" encoding="utf-8"? >
<com.bxll.layoutparamsdemo.CornerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorAccent"
        app:layout_corner="leftBottom" />

</com.bxll.layoutparamsdemo.CornerLayout>
Copy the code

The value of layout_corner is leftBottom, so the View is displayed in the bottom left corner of the CornerLayout, as shown below

Custom layout parameter properties

First you need to create a custom layout_corner property for the CornerLayout

    <declare-styleable name="CornerLayout_Layout">
        <attr name="layout_corner" format="flags" >
            <flag name="leftTop" value="0x01" />
            <flag name="rightTop" value="0x02" />
            <flag name="leftBottom" value="0x04" />
            <flag name="rightBottom" value="0x08" />
        </attr>
    </declare-styleable>
Copy the code

It can be seen that layout_corner has four attribute values, whose names are leftTop, rightTop, leftBottom and rightBottom respectively, and these four names have corresponding hexadecimal values.

Once you define the layout_corner property, you can use the layout property in your XML layout parameters, but only if the parent View is a CornerLayout.

Create the layout parameter class

Now I’ll create a layout parameter class for the CornerLayout that will parse the layout_corner property. I’ll call it CornerLayoutParams. Because I need CornerLayoutParams support margin features, so I choose to let it inherits from the ViewGroup. MarginLayoutParams.

public class CornerLayout extends ViewGroup {
    // Define the layout parameter class
    public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
        // These constants need to correspond to the value of the layout_corner property
        public static final int CORNER_LEFT_TOP = 0x01;
        public static final int CORNER_RIGHT_TOP = 0x02;
        public static final int CORNER_LEFT_BOTTOM = 0x04;
        public static final int CORNER_RIGHT_BOTTOM = 0x08;

        public int mCorner = CORNER_LEFT_TOP;

        public CornerLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CornerLayout_Layout); mCorner = a.getInt(R.styleable.CornerLayout_Layout_layout_corner, CORNER_LEFT_TOP); a.recycle(); }}}Copy the code

Here, I just create a CornerLayoutParams(Context C, AttributeSet Attrs) constructor and parse the value of the layout_corner attribute in that constructor.

There are many more constructors, and this one is necessary because the system will call this constructor to create layout parameters when parsing the layout file.

Implementing an interface

So the question comes, how accurate system know is creating CornerLayout CornerLayoutParams object instead of creating the layout of the other parameters of the class of objects, such as the LinearLayout. LayoutParams. This is, of course, an interface to a system, which is the generateLayoutParams(AttributeSet Attrs) method of the ViewGroup class.

So in CornerLayout autotype. This interface to create CornerLayout CornerLayoutParams object.

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new CornerLayoutParams(getContext(), attrs);
    }
Copy the code

This allows you to happily use the layout_corner layout attribute in your XML layout, as well as attributes like margin. For example, the following layout uses both layout attributes

<?xml version="1.0" encoding="utf-8"? >
<com.umx.layoutparamsdemo.CornerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginEnd="10dp"
        android:layout_marginBottom="10dp"
        android:background="@color/colorAccent"
        app:layout_corner="rightBottom" />

</com.umx.layoutparamsdemo.CornerLayout>
Copy the code

It should look like this

Dynamically add a View

So far, we’ve only implemented the ability to add sub-views to a CornerLayout in an XML layout, but not dynamically.

Dynamically adding child views is done by methods like addView() on a ViewGroup. AddView () can be divided into two classes, depending on whether or not it takes layout argument types, as follows.

// A method with no layout parameter type arguments
public void addView(View child) {}
public void addView(View child, int index) {}
public void addView(View child, int width, int height) {}

// A method with layout parameter type parameters
public void addView(View child, LayoutParams params) {}
public void addView(View child, int index, LayoutParams params) {}
Copy the code

If you use the addView() method with no layout parameter type arguments, the system needs to create a layout parameter object. Take addView(View Child, int Index) as an example

    public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        // Get the layout parameter object
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            // If no layout parameter object exists, create a default
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }
Copy the code

As you can see, if you add a child of the View no layout parameter object, then is called generateDefaultLayoutParams () method to create a default layout parameters of the object.

Therefore CornerLayout if need to support the dynamic characteristics of the add View, you will need to autotype generateDefaultLayoutParams () method, in this method the need to provide the most basic of high properties, wide and mCorner default values.

public class CornerLayout extends ViewGroup {
    
    /** * The system creates the interface for default layout parameters. * /
    @Override
    protected LayoutParams generateDefaultLayoutParams(a) {
        return new CornerLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }    

    /** * defines the layout parameter class. * /
    public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
        public int mCorner = CORNER_LEFT_TOP;

        public CornerLayoutParams(int width, int height) {
            super(width, height);
            // Since mCorner has a default value, it is no longer provided here}}}Copy the code

Regardless of whether the addView() method takes arguments of the layout parameter type, the addViewInner() method is eventually called to implement this, and the addViewInner() method corrects objects of the layout parameter type as appropriate

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        // ...
        
        // If the layout parameter object is invalid, the object needs to be corrected
        if(! checkLayoutParams(params)) { params = generateLayoutParams(params); }// ...
    }
Copy the code

The checkLayoutParams() method is used to check that the layout parameter type is valid. For a CornerLayout, you need to check that the object type is CornerLayoutParams

    /** * Check whether the parameter type is valid. * /
    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        return p instanceof CornerLayoutParams;
    }
Copy the code

If the layout parameter object is invalid, generateLayoutParams(LayoutParams P) is called to correct it by extracting the desired layout properties, such as layout_widht, layout_height, and margin.

The CornerLayout implementation of generateLayoutParams(LayoutParams P) looks like this

public class CornerLayout extends ViewGroup {

    /** * Re-create the CornerLayoutParams object with the invalid parameter p. * /
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        if (p instanceof MarginLayoutParams) {
            return new CornerLayoutParams((MarginLayoutParams)p);
        }
        return new CornerLayoutParams(p);
    }

    /** * define layout parameters. * /
    public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
        public int mCorner = CORNER_LEFT_TOP;

        public CornerLayoutParams(MarginLayoutParams source) {
            // Call the parent constructor to parse the layout_width, layout_height, and margin attributes
            super(source);
            // mCorner has a default value, so there is no need to provide a default value here
        }

        public CornerLayoutParams(LayoutParams source) {
            // Call the superclass constructor to parse the layout_width and layout_heigth properties
            super(source);
            // mCorner has a default value, so there is no need to provide a default value here}}}Copy the code

At this point, we have implemented the ability to dynamically add child Views for the CornerLayout.

Test dynamically adding child Views

Suppose mainActivity.java loads with the following layout

<? The XML version = "1.0" encoding = "utf-8"? > <com.umx.layoutparamsdemo.CornerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/corner_layout" android:layout_width="match_parent" android:layout_height="match_parent"> </com.umx.layoutparamsdemo.CornerLayout>Copy the code

It’s very simple, just a CornerLayout. Now let’s go into the code and dynamically create a View object and add it to the CornerLayout

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CornerLayout cornerLayout = findViewById(R.id.corner_layout);
        Create a View object with a red background
        View view = new View(this);
        view.setBackgroundColor(getResources().getColor(R.color.colorAccent));
        // Create a layout parameter object
        CornerLayout.CornerLayoutParams layoutParams =
                new CornerLayout.CornerLayoutParams(300.300);
        // Set the layout_corner value
        layoutParams.mCorner = CornerLayout.CornerLayoutParams.CORNER_RIGHT_TOP;
        // Set margin
        layoutParams.rightMargin = 30;
        layoutParams.topMargin = 40;
        // Set layout parameters
        view.setLayoutParams(layoutParams);
        // Add to the CornerLayoutcornerLayout.addView(view); }}Copy the code

You create a View object, set the layout_corner and margin properties, and add the View object to the CornerLayout. The effect is that the View is displayed in the upper right corner with a rightMargin of 30px and a topMargin of 40px.

Reference code

The complete CornerLayout code is provided below for your reference

public class CornerLayout extends ViewGroup {
    public CornerLayout(Context context) {
        this(context, null);
    }

    public CornerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            // Layout only the first child View
            View first = getChildAt(0);
            CornerLayoutParams layoutParams = (CornerLayoutParams) first.getLayoutParams();
            int left;
            int top;
            switch (layoutParams.mCorner) {
                case CornerLayoutParams.CORNER_LEFT_TOP:
                    left = getPaddingLeft() + layoutParams.leftMargin;
                    top = getPaddingTop() + layoutParams.topMargin;
                    break;

                case CornerLayoutParams.CORNER_RIGHT_TOP:
                    top = getPaddingTop() + layoutParams.topMargin;
                    left = getWidth() - getPaddingRight() - first.getMeasuredWidth() - layoutParams.rightMargin;
                    break;

                case CornerLayoutParams.CORNER_LEFT_BOTTOM:
                    top = getHeight() - getPaddingBottom() - first.getMeasuredHeight() - layoutParams.bottomMargin;
                    left = getPaddingLeft() + layoutParams.leftMargin;
                    break;

                case CornerLayoutParams.CORNER_RIGHT_BOTTOM:
                    top = getHeight() - getPaddingBottom() - layoutParams.bottomMargin - first.getMeasuredHeight();
                    left = getWidth() - getPaddingRight() - layoutParams.rightMargin - first.getMeasuredWidth();
                    break;

                default: left = getPaddingLeft() + layoutParams.leftMargin; top = getPaddingTop() + layoutParams.topMargin; } first.layout(left, top, left + first.getMeasuredWidth(), top + first.getMeasuredHeight()); }}/** * The interface where the system creates layout parameters */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new CornerLayoutParams(getContext(), attrs);
    }

    /** * The system creates the interface for default layout parameters. * /
    @Override
    protected LayoutParams generateDefaultLayoutParams(a) {
        return new CornerLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    /** * Check whether the parameter type is valid. * /
    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        return p instanceof CornerLayoutParams;
    }

    /** * Re-create the CornerLayoutParams object with the invalid parameter p. * /
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        if (p instanceof MarginLayoutParams) {
            return new CornerLayoutParams((MarginLayoutParams)p);
        }
        return new CornerLayoutParams(p);
    }

    /** *  Define layout parameter classes. * /
    public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
        public static final int CORNER_LEFT_TOP = 0x01;
        public static final int CORNER_RIGHT_TOP = 0x02;
        public static final int CORNER_LEFT_BOTTOM = 0x04;
        public static final int CORNER_RIGHT_BOTTOM = 0x08;

        public int mCorner = CORNER_LEFT_TOP;

        public CornerLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CornerLayout_Layout);
            mCorner = a.getInt(R.styleable.CornerLayout_Layout_layout_corner, CORNER_LEFT_TOP);
            a.recycle();
        }

        public CornerLayoutParams(int width, int height) {
            super(width, height);
        }

        public CornerLayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public CornerLayoutParams(LayoutParams source) {
            super(source); }}}Copy the code