上一篇文章讲解了如何自定义普通的View,本文接着讲如何自定义ViewGroup。
在之前的工程中创建一个类MyViewGroup,继承自ViewGroup,重写它的三个构造方法及onLayout方法,这几个方法都是要求必须实现的。
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childTop = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
LayoutParams params = (LayoutParams) childView.getLayoutParams();
int marginTop = params.topMargin;
int marginLeft = params.leftMargin;
int marginRight = params.rightMargin;
int marginBottom = params.bottomMargin;
childView.layout(marginLeft,childTop+marginTop-10,
childView.getMeasuredWidth()+marginRight,
childTop+childView.getMeasuredHeight()+marginBottom);
childTop += childView.getMeasuredHeight()+marginTop+marginBottom;
}
}
}
在自定义普通的View的时候除了三个构造方法外,其他方法Android不强制要求重写,但是正常都要写onDraw方法,因为不写的话,系统不知道你要画的是什么形状的View,会导致看不到View。但是在自定义ViewGroup中,因为它需要为子View进行布局设置,所以必须重写onLayout方法。
重写onLayout的具体逻辑根据需求而定,正常情况下,都需要获得子View的个数,然后遍历去调用layout方法去给子View进行布局。可以看到重写的MyViewGroup,我是在onLayout方法中对子View的margin值进行处理,这个我感觉也是可以的,当然在重写onMeasure方法,然后在里面处理子View的margin值也可以。上述代码非常简单,这个ViewGroup有点类似于LinearLayout的垂直布局。childTop记录了当前子View前面的自 View占据的高度。这就完成了对子View的布局。我们再来看到下面的代码:
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(),attrs);
}
public static class LayoutParams extends MarginLayoutParams{
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(ViewGroup.LayoutParams params){
super(params);
}
public LayoutParams(int width,int height){
super(width,height);
}
}
在onLayout方法,我们处理了子View的margin值。margin值的获取,是通过调用childView.getLayoutParams()来得到的,但是它返回的是ViewGroup.LayoutParams,我们看它的源码发现它是没有margin值对应的属性的。所以要想获得margin属性值,我们需要的是MarginLayoutParams。而要想childView.getLayoutParams()返回的是MarginLayoutParams,我们需要重写上述三个方法,使的它们返回MarginLayoutParams或者MarginLayoutParams的子类,我这里返回MarginLayoutParams的子类,其实直接返回MarginLayoutParams也是可以的。MarginLayoutParams类定义在ViewGroup中,在这个MarginLayoutParams类里面才会加载子View的margin属性值,从而进行处理。
下面来看MyViewGroup的onMeasure方法代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = 0;
int measureHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec,heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0){
setMeasuredDimension(widthSpaceSize,measureHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > measureWidth){
measureWidth = childView.getMeasuredWidth(); //子View最大的的宽度
}
measureHeight += childView.getMeasuredHeight();//子View高度和
}
setMeasuredDimension(measureWidth,measureHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > measureWidth) {
measureWidth = childView.getMeasuredWidth(); //子View最大的的宽度
}
}
setMeasuredDimension(measureWidth,heightSpaceSize);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureHeight += childView.getMeasuredHeight();//子View高度和
}
setMeasuredDimension(widthSpaceSize,measureHeight);
}
}
代码首先获取子View的个数,然后调用ViewGroup提供的measureChildren方法开始测量子View的大小。然后根据父容器的测量模式得到MyViewGroup的宽高,在调用setMeasuredDimension方法设置它的全局宽高值。一般自定义ViewGroup不需要重写onDraw,这里我也没有记录。到这,自定义ViewGroup也讲完了。