由上图可知,ViewGroup继承自View,但他又是一个管理view的容器,我们在写布局xml的时候,会告诉容器需要设置的属性(凡是以layout为开头的属性,都是用于告诉容器的,例如layout_width),viewGroup的作用就是给出childView建议的宽和高、测量模式、childView的位置等。之所以是建议的宽和高,是因为childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
View的职责是根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高(onMeasure中完成),同时还有个更重要的职责——在ViewGroup为其指定的区域内绘制自己的形态(onDraw中完成)。
当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;在 RelativeLayout中的childView却没有 layout_gravity,layout_weight,原因就在于每个ViewGroup需要指定一个LayoutParams,用于确定childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看 LinearLayout的源码,会发现其内部定义了LinearLayout.LayoutParams,在此类中,你可以发现weight和 gravity的身影
扩展ViewGroup类需要实现的方法:
onMeasure——计算控件极其子控件所占的区域
onLayout——布局子控件
dispatchDraw——绘制布局控件
super.onMeasure()调用后将可以得到控件的宽高。
控件的onMeasure(),onLayout()方法会被执行多次,注意这里面执行的逻辑不要被多次执行了。
要绘制控件,分为两步:
1.在onMeasure()方法中绘制好控件的大小
2.在onLayout方法里布局好控件的位置用view.layout(left,top,right,bottm)
在OnMeasure(int widthMeasureSpec, int heightMeasureSpec)中
的两个参数widthMeasureSpec,heightMeasureSpec里包含了控件的宽高和测量模式。
测量模式有MeasureSpec.EXACTLY,MeasureSpec.ATMOST,MeasureSpec.EXACTLY,MeasureSpec.UNSPECIFIED
得出的测量模式和在布局文件里的layout-width ,layout-height设置的属性有关
当属性值为fill_parent、match_paren、具体数值时,mode为MeasureSpec.EXACTLY
当属性值为wrap_content时,mode为MeasureSpec.ATMOST
MeasureSpec.UNSPECIFIED模式很少用,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中,意思为想设置多大就多大
最后可以为空间指定大小 setMeasuredDimension();这个方法里的参数是数值,不包含测量模式
还有另外一种方法去控制控件的大小
leftlayout.measure(sideWidth, heightMeasureSpec);
这里的参数是带有测量模式的
int sideWidth=MeasureSpec.makeMeasureSpec((int)(MaxWidth*0.5),MeasureSpec.EXACTLY);
这个是制作带有测量模式的长度值
还是盗用一下鸿洋大神的例子,定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。
对于这个例子,我们只需要ViewGroup能够支持margin即可,因此直接使用系统的MarginLayoutParams
2、onMeasure
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
重写父类的该方法,返回MarginLayoutParams的实例,这样就为我们的ViewGroup指定了其LayoutParams为MarginLayoutParams。
在onMeasure中计算childView的测量值以及模式,以及设置自己的宽和高:
/**
* 计算所有ChildView的宽度和高度,然后根据ChildView的计算结果,设置自己的宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* 获得此ViewGroup上级容器为其设置的宽和高,以及测量模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
// 通过ViewGroup的measureChildren方法为其所有的childrenView设置宽和高
measureChildren(widthMeasureSpec, heightMeasureSpec);
/**
* 记录如果是wrap_content时,设置的宽和高
*/
int width = 0;
int height = 0;
int cCount = getChildCount();
int cWidth = 0;
int cHeight = 0;
MarginLayoutParams cParams = null;
// 用于计算左边两个childView的高度
int lHeight = 0;
// 用于计算右边两个childView的高度,最终高度取二者之间大值
int rHeight = 0;
// 用于计算上边两个childView以及设置的margin的所确定的宽度
int tWidth = 0;
// 用于计算下面两个childiew以及设置的margin的所确定的宽度,最终宽度取二者之间大值
int bWidth = 0;
/**
* 根据childView计算得出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时
*/
for (int i = 0; i < cCount; i++) {
View childView = getChildAt(i);
cWidth = childView.getMeasuredWidth();
cHeight = childView.getMeasuredHeight();
cParams = (MarginLayoutParams) childView.getLayoutParams();
// 上面两个childView确定的宽度
if (i == 0 || i == 1) {
tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
}
// 下面两个childView确定的宽度
if (i == 2 || i == 3) {
bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
}
// 左边两个childView确定的高度
if (i == 0 || i == 2) {
lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
}
// 右边两个childView确定的高度
if (i == 1 || i == 3) {
rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
}
}
// 取最大值
width = Math.max(tWidth, bWidth);
height = Math.max(lHeight, rHeight);
/**
* 如果是精确值或match_parent,设置为父容器计算的值,否则设置为我们计算的值
*
*/
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
: width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
: height);
}
在获取该ViewGroup父容器为其设置的计算模式和尺寸时,通常情况下,只要不是wrap_content,父容器都能正确的计算其尺寸。所以如果设置为wrap_content时,就需要通过其childView的宽和高来进行计算。
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
// TODO Auto-generated method stub
int cCount = getChildCount();
int cWidth = 0; //childrenView的宽度
int cHeight = 0;//childrenView的高度
MarginLayoutParams cParams = null;
/**
* 遍历所有childView根据其宽和高,以及margin进行布局
*/
for (int i = 0; i < cCount; i++) {
View childView = getChildAt(i);
cWidth = childView.getMeasuredWidth();
cHeight = childView.getMeasuredHeight();
cParams = (MarginLayoutParams) childView.getLayoutParams();
int ct = 0;//顶部位置
int cr = 0;//右边位置
int cb = 0;//底部位置
switch (i) {
case 0:
cl = cParams.leftMargin;
ct = cParams.topMargin;
break;
case 1:
cl = getWidth() - cWidth - cParams.leftMargin
- cParams.rightMargin;
ct = cParams.topMargin;
break;
case 2:
cl = cParams.leftMargin;
ct = getHeight() - cHeight - cParams.bottomMargin;
break;
case 3:
cl = getWidth() - cWidth - cParams.leftMargin
- cParams.rightMargin;
ct = getHeight() - cHeight - cParams.bottomMargin;
break;
}
cr = cl + cWidth;
cb = cHeight + ct;
// 按左上右下的顺序给出边距,确定childView布局范围
childView.layout(cl, ct, cr, cb);
}
}
4、使用方案
布局1:
<com.example.view.CustomViewGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#AA333333" >
<TextView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#FF0000"
android:gravity="center"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00ff00"
android:gravity="center"
android:text="1"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#0000FF"
android:gravity="center"
android:text="2"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00FFff"
android:gravity="center"
android:text="3"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
</com.example.view.CustomViewGroup>
效果:
<com.example.view.CustomViewGroup
android:layout_width="200dp"
android:layout_height="300dp"
android:background="#AA333333" >
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#FF0000"
android:gravity="center"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00ff00"
android:gravity="center"
android:text="1"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#0000FF"
android:gravity="center"
android:text="2"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00FFff"
android:gravity="center"
android:text="3"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
</com.example.view.CustomViewGroup>
效果:
布局3:
<com.example.view.CustomViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#AA333333" >
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#FF0000"
android:gravity="center"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00ff00"
android:gravity="center"
android:text="1"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#0000FF"
android:gravity="center"
android:text="2"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00FFff"
android:gravity="center"
android:text="3"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
</com.example.view.CustomViewGroup>
效果:
现在无论ViewGroup的宽和高的值如何定义,我们都实现了预期的效果,其实根据这个例子,我们完全可以写一个自己的LinearLayout,学习最重要的就是融汇贯通,大家研究下吧
参考:
http://blog.csdn.net/lmj623565791/article/details/38339817