在我们进行android开发的时候虽然官方提供了形形色色的控件,但是有的时候根据不同业务需求我们找不到官方的控件支持,那么这个时候就需要我们自己去定义控件来适应不同的需求了.本篇将和大家一起探讨自定义ViewGrop 的相关知识.
转载请注明出处: http://blog.csdn.net/unreliable_narrator?viewmode=contents
首先我们先来看看官方文档是如何进行描述的:
翻译过来的大体意思就是:一个ViewGroup是一种特殊的视图可以包含其他视图(称为孩子)的视图组基类的布局和视图的容器。这个类也定义了viewgroup.layoutparams类作为基类的布局参数。也就是说ViewGroup实际上就是存放一些控件的容器,比如官方自带的一些Linerlayout,RelativeLayout等等.我们先来讲讲ViewGroup中两个重要的方法:onLayout和onMeasure,onLayout是必须重写实现的.
AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
setMeasureDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
//可作为模板代码!
private int measureWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode == MeasureSpec.EXACTLY){//精确值模式,指定具体数值
result = specSize;
}else{
result = 200;//先设置一个默认大小
//最大值模式,layout_width 或 layout_height 为 wrap_content 时,控件大小随控件的内容变化而变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result, specSize);//取出我们指定的大小和 specSize 中最小的一个来作为最后的测量值
}
//MeasureSpec.UNSPECIFIED 不指定其大小,View 想多大就多大
}
return result;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childLeft = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
}
public class MyView extends ViewGroup { public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //调用该方法预先对子控件进行测量 measureChildren(widthMeasureSpec, heightMeasureSpec); //设置控件的宽高 setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } /** * 返回控件的宽 * * @param widthMeasureSpec * @return */ private int measureWidth(int widthMeasureSpec) { int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); int result = 0; //判断是否是包裹内容的模式 if (specMode == MeasureSpec.AT_MOST) { int size = 0; //将所有的子控件的宽度进行叠加 for (int x = 0; x < getChildCount(); x++) { View child = getChildAt(x); int measuredWidth = child.getMeasuredWidth(); size += measuredWidth; } result = size; } else { result = specSize; } return result; } /** * 返回控件的高 * * @param heightMeasureSpec * @return */ private int measureHeight(int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int result = 0; //判断是否是包裹内容 if (heightMode == MeasureSpec.AT_MOST) { for (int x = 0; x < getChildCount(); x++) { View child = getChildAt(x); int measuredHeight = child.getMeasuredHeight(); //取子控件最大的高度 int min = Math.max(result, measuredHeight); result = min; } } else { result = heightSize; } return result; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int left = 0; //左边的距离 View child; //遍历布局子元素 for (int i = 0; i < childCount; i++) { child = getChildAt(i); int width = child.getMeasuredWidth(); child.layout(left, 0, left + width, child.getMeasuredHeight()); left += width; } } }布局文件:
<com.dapeng.viewgropdemo.MyView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/black"> <View android:layout_width="50dip" android:layout_height="50dip" android:background="@android:color/holo_green_dark"></View> <View android:layout_width="100dip" android:layout_height="100dip" android:background="@android:color/holo_red_light"></View> <View android:layout_width="200dip" android:layout_height="200dip" android:background="@android:color/holo_orange_light"></View> </com.dapeng.viewgropdemo.MyView>
参数 1. AttributeSet attrs xml解析inflate时生成和容器类型匹配的布局LayoutParams
2. ViewGroup.LayoutParams p 传入viewGroupLayoutParams 然后生成和容器类型匹配的布局LayoutParams
总结:
这个方法主要是用于被子View调用。
生成和此容器类型相匹配的布局参数类。
@Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected LayoutParams generateLayoutParams( LayoutParams p) { return new MarginLayoutParams(p); }
cParams = (MarginLayoutParams) childView.getLayoutParams(); int bottomMargin = cParams.bottomMargin; int topMargin = cParams.topMargin; int leftMargin = cParams.leftMargin; int rightMargin = cParams.rightMargin;
/**
* 使用自定义LayoutParams必须重写下面的四个方法
*/
@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);
}