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