请优先阅读
Android 之 自定义控件 之 View
http://www.jianshu.com/p/f49c3cd844dc
介绍
ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性,都是为用于告诉容器的),我们的宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity)等;当然还有margin等;于是乎,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
方法介绍
onLayout:用于确定子view在布局中的位置
onMeasure:用于计算和谁在ViewGroup的宽高
generateLayoutParams:生成的LayoutParams 如果我们需要支持margin,需要使用系统的MarginLayoutParams。
View的3种测量模式
上面提到了ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:
EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
如何定义
1.创建自己的ViewGroup
public class MyViewGroup extends ViewGroup {
private Context context;
public MyViewGroup(Context context) {
super(context);
this.context = context;
}
//如果通过XML配置 需要重写带属性的方法
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
}
2.重写onMeasure方法,测量指定容器大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
// 遍历每个子元素 测量子控件的宽高
for (int i = 0; i < getChildCount(); i++)
{
View child = getChildAt(i);
// 测量每一个child的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的lp
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 当前子空间实际占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
// 当前子空间实际占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
//总高度
height+=childHeight;
}
//确认模式
if(heightMode==MeasureSpec.AT_MOST){
setMeasuredDimension(sizeWidth,cHeight);
}else{
setMeasuredDimension(sizeWidth,sizeHeight);
}
}
因为需要获Margin所以需要重写该方法生成我们的LayoutParams,否则会报出 类型转换异常
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
3.onLayout对所有的childView进行布局
/**
* 定位子控件位置 其参数 用于自身对于 外控件的 坐标
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int paddingBottom = getPaddingBottom();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int childCount = getChildCount();
int cWidth = 0;
cHeight = 0;
for (int i=0;i<childCount;i++){
View childView = getChildAt(i);
// int width = childView.getWidth(); 与 getMeasuredWidth 区别在于
// 都是获取控件宽度 实现方式不一样,其次 getWidth在onMeasure 中无法获取到
int measuredWidth = childView.getMeasuredWidth();
int measuredHeight = childView.getMeasuredHeight();
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
//设置子View所处的位置
//childView.layout(int l, int t, int r, int b) ; //l:左边 t:上边 r:右边 b:下边 的位置坐标
childView.layout(0,cHeight+layoutParams.topMargin,measuredWidth-paddingRight,cHeight+measuredHeight+layoutParams.topMargin);
cHeight +=layoutParams.topMargin; //追加 magin
cHeight +=measuredHeight; //追加 之前的高度
}
}
至此 试着去写一个 自己的 自定义ViewGroup
资料来源:http://blog.csdn.net/lmj623565791/article/details/38352503/