自定义控件中调用顺序为 onMeasure()->onLayout()->onDraw()
需要调用全部构造方法
1.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
(1)这个方法的作用是确定父控件与动态添加的子控件宽与高。
(2)重写此方法需必须加上setMeasuredDimension(parentWidthPX, parentHeightPX);即父控件的宽高(pix值)
(3)其它方法:childView.measure(childWidth, childHeight); 绘制子控件的宽高。
(4)这里面有一个重要的类:MeasureSpec。其中重要的属性与方法有:
MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。
它有三种模式:(Mode)
UNSPECIFIED(未指定), 父元素不对自元素施加任何束缚,子元素可以得到任意想要的大小;
EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;//固定大小,与fill_parent
AT_MOST(至多),子元素至多达到指定大小的值。 //wrap_parent
它常用的三个函数:
1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)//pix
3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式) ,方法体如下: //因为measure(width, height) 中参数就是这个返回值
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
2. onLayout()
(1)这个方法的作用是控制控件的确切位置,即其中子控件放到父控件中哪个具体位置,其中就一个重要方法:
childView.layout(left, top, right, bottom); 相对于父控件左上右下的具体px点
(2)还有一个方法 view.getMeasureWidth() view.getMeasureHeight() 这两个是得到控件的实际大小,需要在view.measure()方法后才会有值
3. onDraw()
下面是一个自定义LinearLayout,主要作用为动态添加控件,支持自动换行
@see http://blog.csdn.net/long704480904/article/details/9011115
Code:
/**
* 自定义LinearLayout
* 主要作用 支持自动换行
*/
public class CustomLayout extends ViewGroup {
private int childWidth; //子控件的宽
private int childHeight; //子控件的高
private int columns; //动态展示的列数
//构造函数要写全,否则会报错
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 设置需要添加的子控件的 宽、高 此处设置的宽高为最大值 具体显示的值需要看下面方法中mode设置
*
* @param childWidth MeasureSpec.makeMeasureSpec(size, mode) 中的size值
* @see //MeasureSpec.makeMeasureSpec(size, mode)
*/
public void setChild(int childWidth, int childHeight) {
this.childWidth = childWidth;
this.childHeight = childHeight;
}
/**
* 控制子控件的换行
* 参数中 relative to parent 是此LinearLayout相对于它的父控件,即此LinearLayout 的上一个控件
*
* @param changed This is a new size or position for this view
* @param leftPos Left position, relative to parent
* @param topPos Top position, relative to parent
* @param rightPos Right position, relative to parent
* @param bottomPos Bottom position, relative to parent
*/
@Override
protected void onLayout(boolean changed, int leftPos, int topPos, int rightPos, int bottomPos) {
int childTotalWidth = childWidth;
int childTotalHeight = childHeight;
if (columns < 0) {
columns = 1;
}
int curLeft = 0; //当前 距离此控件 的左坐标 , 当换行是需要清零
int curTop = 0; //当前 距离此控件 的 上坐标 , 换行不需要清零
int curColPos = 0; //当前 绘制的第几列,用来控制换行的
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View childView = getChildAt(i);
// 获取子控件Child的实际宽高值, 必须在此之前调用 child.measure() 方法,否则值为0
int childActualWidth = childView.getMeasuredWidth();
int childActualHeight = childView.getMeasuredHeight();
// 计算子控件的顶点坐标,控制居中
int left = curLeft + ((childTotalWidth - childActualWidth) / 2);
int top = curTop + ((childTotalHeight - childActualHeight) / 2);
// 布局子控件 这个方法可以固定指定子控件的左上右下的指定px位置,即可以通过column换行
//重要方法, 左上右下是相对于此控件的
childView.layout(left, top, left + childActualWidth, top + childActualHeight);
if (curColPos >= (columns - 1)) {
curColPos = 0;
curLeft = 0;
curTop += (childTotalHeight + switchDipToPix(10)); //高度差为10dip
} else {
curColPos++;
curLeft += childTotalWidth;
}
}
}
/**
* 计算控件及子控件所占区域
*
* @param widthMeasureSpec size与mode 相加, 这个控件 size 为 px值 mode MeasureSpec.AT_MOST|EXACTLY,wrap_parent|fill_parent or 具体值
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 创建测量参数, 下面的值是实际上控件的宽和高,因为MeasureSpec.AT_MOST的是wrap_parent的意思,即显示min_width,如果背景小于childWidth
// 则有背景的时候,得到的width值为背景width,如果无背景,则显示最小的width
// 如果 mode设置为MeasureSpec.EXACTLY fill_parent的意思,则值等于前面赋予的size值,
int cellWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST);
int cellHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST);
// 记录ViewGroup中Child的总个数
int count = getChildCount();
// 设置子空间Child的宽高
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
childView.measure(cellWidthSpec, cellHeightSpec);
}
// 设置父容器控件所占区域大小
int parentWidthPX = MeasureSpec.getSize(widthMeasureSpec);
columns = parentWidthPX / childWidth; //列数
int lineCounts = 0; //总共行数,用来计算整个LinearLayout的高度
if (columns != 0) {
lineCounts = (count / columns);
if ((count % columns) != 0) { //如果不能整除,证明还需要多一行
lineCounts++;
}
}
int parentHeightPX = (lineCounts * (childHeight + switchDipToPix(10))); //行间距 10dip
//重写onMeasure方法 必须加上 setMeasuredDimension()这个方法来设置这个控件最终的宽高!
setMeasuredDimension(parentWidthPX, parentHeightPX);
// 不需要调用父类的方法
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int switchDipToPix(int dipValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, getResources().getDisplayMetrics());
}
// /**
// * 为控件添加边框
// */
// @Override
// protected void dispatchDraw(Canvas canvas) {
// // 获取布局控件宽高
// int width = getWidth();
// int height = getHeight();
// // 创建画笔
// Paint mPaint = new Paint();
// // 设置画笔的各个属性
// mPaint.setColor(Color.BLUE);
// mPaint.setStyle(Paint.Style.STROKE);
// mPaint.setStrokeWidth(10);
// mPaint.setAntiAlias(true);
// // 创建矩形框
// Rect mRect = new Rect(0, 0, width, height);
// // 绘制边框
// canvas.drawRect(mRect, mPaint);
// // 最后必须调用父类的方法
// super.dispatchDraw(canvas);
// }
}