一.基础知识
在这里首先介绍一下Android下自定义Component的一些基础知识,已经了解或者心急的同学可以直接跳到第二章看代码。
说到自定义Component,我想在没有什么比Google自家的SDK document解释的来的全面和准确的了,下面我们就来看看官方是怎么说的:
在SDK->API Guides->UserInterface->Custom Components一章中官方是这么说的
Android offers a sophisticated and powerful componentized model for building your UI, based on the fundamental layout classes: View andViewGroup. To start with, the platform includes a variety of prebuilt View and ViewGroup subclasses — called widgets and layouts, respectively — that you can use to construct your UI.
也就是说,基本所有我们使用的UI控件都是继承自View和ViewGroup这两个基类的,比如我们常用的布局(Linerlayout,RelativeLayout等),和一些widgets(Button,TextView等)。我们自己想要自定义控件也必须继承这两个基类(当然,你也可以直接继承已有的Layouts和widgets)。
重写ViewGroup需要注意的事项:
1.必须要重写onLayout方法,并且需要实现三个默认的构造函数,不然会加载布局时出错。
2.重写onMeasure方法。
onMeasure方法,该方法指定控件在屏幕上的大小,方法的原型:
@Override protectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){
其中widthMeasureSpec和heightMeasureSpec为父布局的宽高,这两个参数是由上一层控件传入的大小,不过onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
如果要想让ViewGroup在wrap_content下自适应高度。需要在onMeasure中调用setMeasuredDimension(measuredWidth,measuredHeight)把计算好的高度和宽度进行附值。
onLayout方法,该方法通过拿到childView的getMeasuredWidth() andgetMeasuredHeight(),用来布局所有的childView。
二.自动换行的布局(通过自定义ViewGroup实现)
废话不多说,直接上代码:
package com.example.hyydatalist.view;
import com.example.hyydatalist.constants.HyyConstants;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public class AutoExtViewGroup extends ViewGroup {
public AutoExtViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public AutoExtViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public AutoExtViewGroup(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
private final static int VIEW_MARGIN = 10;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
Log.d(HyyConstants.HYY_TAG, "widthMeasureSpec=" + widthMeasureSpec
+ "heightMeasureSpec=" + heightMeasureSpec);
int stages = 1;
int stageHeight = 0;
int stageWidth = 0;
int wholeWidth = MeasureSpec.getSize(widthMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
// measure
measureChild(child, widthMeasureSpec, heightMeasureSpec);
stageWidth += (child.getMeasuredWidth() + VIEW_MARGIN);
stageHeight = child.getMeasuredHeight();
if (stageWidth >= wholeWidth) {
stages++;
//reset stageWidth
stageWidth = child.getMeasuredWidth();
}
Log.i(HyyConstants.HYY_TAG, "i:" + i + ",wholeWidth:" + wholeWidth
+ ",stageWidth:" + stageWidth+",stageHeight:"+stageHeight+",stage:"+stages);
}
Log.i(HyyConstants.HYY_TAG, "stages:" + stages);
int wholeHeight = (stageHeight + VIEW_MARGIN) * stages;
// report this final dimension
setMeasuredDimension(resolveSize(wholeWidth, widthMeasureSpec),
resolveSize(wholeHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
final int count = getChildCount();
int row = 0; // which row lay your view relative to parent
int lengthX = l; // right position of child relative to parent
int lengthY = t; // bottom position of child relative to parent
for (int i = 0; i < count; i++) {
final View child = this.getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
lengthX += width + VIEW_MARGIN;
lengthY = row * (height + VIEW_MARGIN) + VIEW_MARGIN + height + t;
// if it cant't draw in a same line ,skip it to next line
if (lengthX > r) {
lengthX = width + VIEW_MARGIN + l;
row++;
lengthY = row * (height + VIEW_MARGIN) + VIEW_MARGIN + height
+ t;
}
child.layout(lengthX - width, lengthY - height, lengthX, lengthY);
}
}
}