在项目中,有的时候需要用到自定义的ViewGroup
一、绘制过程
绘制布局由两个遍历过程组成:测量过程和布局过程。测
量过程由 measure(int, int) 方法完成,该方法从上到下遍历视图
树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当
measure 方法遍历结束,每个视图都保存了各自的尺寸信息。第二
个过程由 layout(int, int, int,int) 方法完成,该方法也是由上而下遍
历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所
有子视图的位置信息。”
第一步是测量 ViewGroup 的宽度和高度,在 onMeasure() 方法中完成这
步操作。在该方法中, ViewGroup 通过遍历所有子视图计算出它
的大小。在 onLayout() 方法中完成,在该方法中,
ViewGroup 利用上一步计算出的测量信息,布局所有子视图。
二、ViewGroup和LayoutParams
每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等
在编写 onMeasure() 方法之前,先创建自定义 LayoutParams
类,该类用于保存每个子视图的 x、 y 轴位置。把 LayoutParams 定
义为 的内部类
三、View的3种测量模式
EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;
四、自定义ViewGroup需要实现的方法
用新定义的 ViewGroup中的LayoutParams 类,还需要重
写 CascadeLayout 类中的其他一些方法。这些方法是 checkLayoutParams()、 generateDefaultLayoutParams()、 generateLayoutPar
ams(AttributeSetattrs) 和 generateLayoutParams(ViewGroup.
LayoutParams p)。这些方法的代码在不同 ViewGroup 之间往往是
相同的。
效果图
主界面java代码就不贴了,只是引用xml而已
主界面布局activity_hack03.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:cascade="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="boerpower.com.android50hacks.Hack03Activity">
<boerpower.com.android50hacks.CascadeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
cascade:horizontal_spacing="30dp"
cascade:vertical_spacing="20dp">
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#FF0000" />
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#00FF00" />
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#0000FF" />
</boerpower.com.android50hacks.CascadeLayout>
</RelativeLayout>
自定义ViewGroup类:CascadeLayout.java
public class CascadeLayout extends ViewGroup {
private int mHorizontalSpacing;
private int mVerticalSpacing;
public CascadeLayout(Context context, AttributeSet attrs) {//当通过XML文件创建该视图的实例时会调用构造函数
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
//mHorizontalSpacing和mVerticalSpacing由自定义属性中获取,如果其值未指定,就使用默认值
try {
mHorizontalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing, getResources()
.getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
} finally {
a.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//使用宽和高计算布局的最终大小以及子视图的x与y轴位置
int width = getPaddingLeft();
int height = getPaddingTop();
int verticalSpacing;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
verticalSpacing = mVerticalSpacing;
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);//令每个子视图测量自身
LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = getPaddingLeft() + mHorizontalSpacing * i;
//在 LayoutParams中保存每个子视图的 x 和 y 坐标
lp.x = width;
lp.y = height;
if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
}
width += child.getMeasuredWidth();
height += verticalSpacing;
}
width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight()
+ getPaddingBottom();
//在 LayoutParams中保存每个子视图的 x 和 y 坐标使用计算所得的宽和高设置整个布局的 测 量尺寸
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
+ child.getMeasuredHeight());
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout_LayoutParams);
try {
verticalSpacing = a
.getDimensionPixelSize(
R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
-1);
} finally {
a.recycle();
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
}
定义那些定制的属性,在res/values 目录下创建一个属性文件 attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CascadeLayout">
<attr name="horizontal_spacing" format="dimension" />
<attr name="vertical_spacing" format="dimension" />
</declare-styleable>
<declare-styleable name="CascadeLayout_LayoutParams">
<attr name="layout_vertical_spacing" format="dimension" />
</declare-styleable>
</resources>
同时还需要指定水平间距和垂直间距的默认值,以便在未指定这些值时使用。把这些默认值保存在 dimens.xml 文件中,该文件同样位于 res/values 文件夹下。 dimens.xml 文件的内容如下:
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="cascade_horizontal_spacing">10dp</dimen>
<dimen name="cascade_vertical_spacing">10dp</dimen>
</resources>
参考资料
Android 手把手教您自定ViewGroup
Android 自定义ViewGroup 实战篇 -> 实现FlowLayout