假设有下面一个布局文件java
<?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> 复制代码
系统在加载这个布局的时候,会建立一个LinearLayout
对象和一个TextView
对象,而后会调用LinearLayout.addView()
方法保存这个TextView
对象,同时也会建立LinearLayout.LayoutParams
对象来保存TextView
所声明的布局参数,例如layout_width
和layout_height
。这就是你能够在LinearLayout
的子View中声明layout_width
和layout_height
属性的缘由。android
ViewGroup
有两个最基本的布局参数类,ViewGroup.LayoutParams
和ViewGroup.MarginLayoutParams
。markdown
全部的自定义ViewGroup
的布局参数类必需要直接或者间接继承自ViewGroup.LayoutParams
类,由于你须要支持layout_width
和layout_height
布局属性。app
若是你想要让自定义ViewGroup
的布局参数属性支持margin
,例如layout_marginLeft
,那么自定义ViewGroup
的布局参数类须要直接或者间接继承自ViewGroup.MarginLayoutParams
,由于它有解析margin
属性的功能。ide
假设如今有一个自定义ViewGroup
,名字叫作CornerLayout
。CornerLayout
会根据子View的layou_corner
布局属性决定子View放置在哪一个角落。例如如今有下面一个布局函数
<?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> 复制代码
layout_corner
的值为leftBottom
,所以这个View会显示在CornerLayout
的左下角,以下图所示布局
首先须要为CornerLayout
建立一个自定义的layout_corner
属性测试
<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> 复制代码
能够看出layout_corner
有四个属性值,,名字分别为leftTop
,rightTop
,leftBottom
,rightBottom
,而且这个四个名字有对应的十六进制值。this
定义了layout_corner
属性后,就能够在XML布局参数中使用这个布局属性,不过前提是父View必须是CornerLayout
。spa
如今要给CornerLayout
建立一个布局参数类,用来解析layout_corner
属性,我把它命名为CornerLayoutParams
。因为我须要CornerLayoutParams
支持margin
特性,所以我选择让它继承自ViewGroup.MarginLayoutParams
。
public class CornerLayout extends ViewGroup { // 定义布局参数类 public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams { // 这些常量须要与layout_corner的属性值相对应 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(); } } } 复制代码
这里,我只建立了一个构造函数CornerLayoutParams(Context c, AttributeSet attrs)
,而且在这个构造函数中解析出layout_corner
属性值。
其实还有还有不少构造函数,而这一个构造函数是必须的,由于系统在解析布局文件的时候,会调用这个构造函数来建立布局参数。
那么问题来了,系统怎么精确的知道是建立CornerLayout.CornerLayoutParams
对象而不是建立其余的布局参数类的对象,例如LinearLayout.LayoutParams
。固然是复写某个系统的接口,这个接口就是ViewGroup
类的generateLayoutParams(AttributeSet attrs)
方法。
所以在CornerLayout
中复写这个接口来建立CornerLayout.CornerLayoutParams
对象。
@Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new CornerLayoutParams(getContext(), attrs); } 复制代码
如此一来就能够在XML布局中快乐的使用layout_corner
布局属性了,并且也能够使用margin
这一类属性。例以下面一个布局就使用了这两个布局属性
<?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> 复制代码
显示效果以下
到目前为止,咱们只实现了在XML布局中为CornerLayout
添加子View的功能,然而并无实现动态添加子View功能。
动态添加子View是由ViewGroup
的addView()
这一类方法实现的。根据是否带有布局参数类型的参数,能够把addView()
分为两类,以下。
// 不带有布局参数类型参数的方法 public void addView(View child) {} public void addView(View child, int index) {} public void addView(View child, int width, int height) {} // 带有布局参数类型参数的方法 public void addView(View child, LayoutParams params) {} public void addView(View child, int index, LayoutParams params) {} 复制代码
若是你使用的是不带布局参数类型参数的addView()
方法,系统须要建立一个布局参数对象。以addView(View child, int index)
为例来分析下
public void addView(View child, int index) { if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } // 获取布局参数对象 LayoutParams params = child.getLayoutParams(); if (params == null) { // 若是不存在布局参数对象,那么就建立一个默认的 params = generateDefaultLayoutParams(); if (params == null) { throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); } } addView(child, index, params); } 复制代码
能够看到,若是添加的子View没有布局参数对象,那么就会调用generateDefaultLayoutParams()
方法来建立一个默认的布局参数对象。
所以CornerLayout
若是须要支持动态添加View的特性,那么还须要复写generateDefaultLayoutParams()
方法,在这个方法中须要提供最基本的宽高属性,以及mCorner
的默认值。
public class CornerLayout extends ViewGroup { /** * 系统建立默认布局参数的接口。 */ @Override protected LayoutParams generateDefaultLayoutParams() { return new CornerLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } /** * 定义布局参数类。 */ public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams { public int mCorner = CORNER_LEFT_TOP; public CornerLayoutParams(int width, int height) { super(width, height); // 因为mCorner有默认值,所以这里再也不提供默认值 } } } 复制代码
无论addView()
方法是否带有布局参数类型的参数,最终都会调用addViewInner()
方法来实现,而addViewInner()
方法根据状况来校订布局参数类型的对象
private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { // ... // 若是布局参数对象不合法就须要校订对象 if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } // ... } 复制代码
checkLayoutParams()
方法用来检测布局参数类型的对象是否合法,对于CornerLayout
来讲,须要检测这个对象的类型是否为CornerLayoutParams
,所以实现以下
/** * 检测参数类型是否合法。 */ @Override protected boolean checkLayoutParams(LayoutParams p) { return p instanceof CornerLayoutParams; } 复制代码
若是布局参数对象不合法,那么就须要调用generateLayoutParams(LayoutParams p)
来校订,而校订的方式是抽取须要的布局属性,例如layout_widht
, layout_height
,以及margin
。
那么,CornerLayout
的generateLayoutParams(LayoutParams p)
的实现以下
public class CornerLayout extends ViewGroup { /** * 根据不合法的参数p, 从新建立CornerLayoutParams对象。 */ @Override protected LayoutParams generateLayoutParams(LayoutParams p) { if (p instanceof MarginLayoutParams) { return new CornerLayoutParams((MarginLayoutParams)p); } return new CornerLayoutParams(p); } /** * 定义布局参数。 */ public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams { public int mCorner = CORNER_LEFT_TOP; public CornerLayoutParams(MarginLayoutParams source) { // 调用父类构造函数解析layout_width, layout_height, margin属性 super(source); // mCorner有默认值,所以这里不须要再提供默认值 } public CornerLayoutParams(LayoutParams source) { // 调用父类构造函数,解析layout_width和layout_heigth属性 super(source); // mCorner有默认值,所以这里不须要再提供默认值 } } } 复制代码
至此,咱们已经为CornerLayout
实现了动态添加子View的功能。
假设MainActivity.java
加载的布局以下
<?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>
复制代码
很简单,就一个CornerLayout
布局。如今咱们到代码中动态建立一个View对象,并添加到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); // 建立一个红色背景的View对象 View view = new View(this); view.setBackgroundColor(getResources().getColor(R.color.colorAccent)); // 建立布局参数对象 CornerLayout.CornerLayoutParams layoutParams = new CornerLayout.CornerLayoutParams(300, 300); // 设置layout_corner值 layoutParams.mCorner = CornerLayout.CornerLayoutParams.CORNER_RIGHT_TOP; // 设置margin值 layoutParams.rightMargin = 30; layoutParams.topMargin = 40; // 设置布局参数 view.setLayoutParams(layoutParams); // 添加到CornerLayout中 cornerLayout.addView(view); } } 复制代码
代码中建立了一个View对象,而且设置了layout_corner
以及margin
属性,最后把这个View对象添加到CornerLayout
布局中。实际效果就是View显示在右上角,而且rightMargin
为30px
,topMargin
为40px
。
下面给出完整的CornerLayout
代码,以供参考
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) { // 只对第一个子View进行layout 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()); } } /** * 系统建立布局参数的接口 */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new CornerLayoutParams(getContext(), attrs); } /** * 系统建立默认布局参数的接口。 */ @Override protected LayoutParams generateDefaultLayoutParams() { return new CornerLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } /** * 检测参数类型是否合法。 */ @Override protected boolean checkLayoutParams(LayoutParams p) { return p instanceof CornerLayoutParams; } /** * 根据不合法的参数p, 从新建立CornerLayoutParams对象。 */ @Override protected LayoutParams generateLayoutParams(LayoutParams p) { if (p instanceof MarginLayoutParams) { return new CornerLayoutParams((MarginLayoutParams)p); } return new CornerLayoutParams(p); } /** * 定义布局参数类。 */ 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); } } } 复制代码