先看效果图:
上图的小方块是自定义的View
包裹小方块的是一个自定义的ViewGroup
为了简便,我将自定义View写成了自定义ViewGroup的内部类形式:
public class ViewGroupDemo extends ViewGroup {
Random random = new Random();
Paint paint = new Paint();
private Context context;
int lie = 3; // 一行三个(列数)
int padding = 10; // padding
public ViewGroupDemo(Context context) {
super(context);
this.context = context;
paint.setStyle(Paint.Style.FILL);
}
public ViewGroupDemo(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
paint.setStyle(Paint.Style.FILL);
}
public ViewGroupDemo(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
paint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height;
// xml中的宽度是match_parent。属于精确布局
int width = measureDimension(MeasureSpec.getSize(widthMeasureSpec),widthMeasureSpec);
// 算出子View的宽度
int childWidth = (width - (lie + 1) * padding) / lie;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else {
int defHeight = ((int) Math.ceil(getChildCount() / (double)lie)) * (childWidth + padding) + padding;
height = measureDimension(defHeight,heightMeasureSpec);
}
for (int i = 0; i < getChildCount(); i++) { // 分别计算每个子视图的宽高
// 因为是正方形小块,子视图宽高一样。这里设置的值,会被传到子view的onMeasure()方法中
getChildAt(i).measure(childWidth, childWidth);
}
setMeasuredDimension(width, height); // 设置Group的宽高
// 默认情况下,match_parent 或者 wrap_content 没有任何区别,其显示大小由父控件决定,它会填充满整个父控件的空间。
// 固定的值,则其显示大小为该设定的值。
// 如果你的自定义控件的大小计算就是跟系统默认的行为一致的话,那么你就不需要重写onMeasure函数了。
// super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int height = padding;
// 遍历子视图,为每个视图设置布局位置
for (int i = 0; i < getChildCount(); i++) {
int childWidth = getChildAt(i).getMeasuredWidth();
int childHeight = getChildAt(i).getMeasuredHeight();
if (i % lie == 0 && i != 0)
height += (childHeight + padding);
int left = (i % lie) * padding + padding + (i % lie) * childWidth;
int top = height;
int right = (i % lie) * padding + padding + (i % lie) * childWidth + childWidth;
int bottom = height + childHeight;
// 四个参数分别为这个子视图的左、上、右、下的位置坐标。这样就能确定这个子视图的位置信息
getChildAt(i).layout(left, top, right, bottom);
}
}
/**
* 改变视图中的方块个数和每行个数
* @param count
* @param lie
*/
public void change(int count, int lie) {
this.removeAllViews();
this.lie = lie;
for (int i = 0; i < count; i++) {
this.addView(new MyView(context));
}
}
protected int measureDimension(int defaultSize,int measureSpec ) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
//1. layout给出了确定的值,比如:100dp
//2. layout使用的是match_parent,但父控件的size已经可以确定了,比如设置的是具体的值或者match_parent
if (specMode == MeasureSpec.EXACTLY) {
result = specSize; //建议:result直接使用确定值
}
//1. layout使用的是wrap_content
//2. layout使用的是match_parent,但父控件使用的是确定的值或者wrap_content
else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, specSize); //建议:result不能大于specSize
}
//UNSPECIFIED,没有任何限制,所以可以设置任何大小
//多半出现在自定义的父控件的情况下,期望由自控件自行决定大小
else {
result = defaultSize;
}
return result;
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
// 随机设置paint的颜色,每次绘制界面时都会设置随机颜色
paint.setARGB(random.nextInt(100) + 126, random.nextInt(256), random.nextInt(256), random.nextInt(256));
// 画一个和View大小相等的矩形
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 测量View的宽高
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}
}
}
Activity的代码如下:
public class MainActivity extends Activity {
private EditText et_count;
private ViewGroupDemo groupDemo;
private EditText et_lie;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_count = (EditText) findViewById(R.id.et_count);
et_lie = (EditText) findViewById(R.id.et_lie);
groupDemo = (ViewGroupDemo) findViewById(R.id.mygroup);
groupDemo.change(5,3); // 默认个数为5个,三列
}
public void click(View v) {
String value = et_count.getText().toString();
String lie = et_lie.getText().toString();
try {
int count = Integer.parseInt(value);
int l = Integer.parseInt(lie);
if (count > 0 && count <= 100) {
if (l<=20&&l>0){
groupDemo.change(count,l);
}else {
Toast.makeText(MainActivity.this,"输入错误",Toast.LENGTH_LONG).show();
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
布局代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/et_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="个数最多100"
android:inputType="number" />
<EditText
android:id="@+id/et_lie"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="列数,最多20"
android:inputType="number" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="click"
android:text="变换" />
</LinearLayout>
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<cn.pan.viewgroupdemo.ViewGroupDemo
android:id="@+id/mygroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"></cn.pan.viewgroupdemo.ViewGroupDemo>
</ScrollView>
</LinearLayout>