ViewGroup 运行的基本流程大致为:
1) 测量容器尺寸
重写 onMeasure()方法测量容器大小,和自定义组件有所区别的是,在测量容器大小之前,必须先调用 measureChildren()方法测量所有子组件的大小,不然结果永远为 0。
2) 确定每个子组件的位置
重写 onLayout()方法确定每个子组件的位置(这个其实挺麻烦,也是定义容器的难点部
分),在 onLayout()方法中,调用 View 的 layout()方法确定子组件的位置。
3) 绘制容器
重写 onDraw()方法,其实 ViewGroup 类并没有重写 onDraw()方法,除非有特别的要求,
自定义容器也很少去重写。比如 LinearLayout 重写了该方法用于绘制水平或垂直分割
条,而 FrameLayout 则是重写了 draw()方法,作用其实是一样的。
工作原理:
重写 onMeasure()方法:
重写 ViewGroup 的 onMeasure()方法时,必须先调用 measureChildren()方法测量子组件的尺寸,measureChildren()方法中,循环遍历每一个子组件,如果当前子组件的可见性不为 GONE 也就是没有隐藏则继续调用 measureChild(child,widthMeasureSpec,heightMeasureSpec)方法测量当前子组件 child 的大小,measureChild()方法结合父容器的 MeasureSpec、子组件的 Padding 和 LayoutParams 三个因素 利 用 getChildMeasureSpec() 计算出子组件的尺寸模式和尺寸大小(可以跟踪到getChildMeasureSpec()方法中查看),并调用子组件的 measure()方法进行尺寸测量。measure()方法调用了 onMeasure(widthMeasureSpec,heightMeasureSpec)方法,该方法正是我们重用的用来测量组件尺寸的方法,至此,测量组件尺寸的工作已掌握到开发人员手中。
接下来调用 onLayout()方法定位子组件,以确定子组件的位置和大小,在 onLayout()方法中,我们将调用子组件的 layout()方法,这里要一分为二,如果子组件是一个 View,定位流程到此结束,如果子组件又是一个容器呢?我们进入 layout()方法进行跟踪。如果子组件是一个容器,又会继续调用该容器的 onLayout()方法对孙组件进行定位,所以onLayout()方法也是一个递归的过程。
重写 onLayout()方法:
protected void onLayout(boolean changed,int l,int t,int r,int b),其中,参数 changed 判断是否有新的大小和位
置,l 表示 left,t 表示 top,r 表示 right,b 表示 bottom,后面的 4 个参数表示容器自己相对父容器的位置以及自身的大小,通常情况下,r-l 的值等同于方法 getMeasuredWidth()方法的返回值,b-t 的值等同于 getMeasuredHeight()方法的返回值。在 onLayout()方法中,需要调用 View 的 layout()方法用于定义子组件和子容器的位置,layout()方法的原理如下:
public void layout(int l,int t,int r,int b)
展示:
public class CustomViewGroup extends ViewGroup {
private TextView textView;
public CustomViewGroup(Context context) {
super(context);
}
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void init(Context context){
textView = new TextView(context);
textView.setText("自定义ViewGroup");
textView.setTextColor(Color.parseColor("#00a0e9"));
textView.setBackgroundColor(Color.YELLOW);//TextView的背景
ViewGroup.LayoutParams lp = new LayoutParams(400,400);//textview的布局参数
addView(textView,lp);
setBackgroundColor(Color.BLUE);//ViewGroup的背景颜色
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View childView = getChildAt(0);
childView.layout(50,50,textView.getMeasuredWidth()+50,textView.getMeasuredHeight()+50);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);//测量子View
setMeasuredDimension(600,600);//测量ViewGroup的宽高
}
}
结果显示:
public class CustomViewGroup2 extends ViewGroup {
public CustomViewGroup2(Context context) {
super(context);
}
public CustomViewGroup2(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomViewGroup2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量所有的子View
measureChildren(widthMeasureSpec, heightMeasureSpec);
//测量自己的宽高
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureHeight(int widthMeasureSpec) {
//父控件建议自己测量的值
int measureMode = MeasureSpec.getMode(widthMeasureSpec);
int measureSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
if (measureMode == MeasureSpec.EXACTLY) {
width = measureSize;
} else if (measureMode == MeasureSpec.AT_MOST) {
int aWidth = 0;
int bWidth = 0;
int cWidth = 0;
int dWidth = 0;
for (int i = 0; i < getChildCount(); i++) {
if (i == 0) {
aWidth = getChildAt(i).getMeasuredWidth();//左边View的宽度
} else if (i == 1) {
bWidth = getChildAt(i).getMeasuredWidth();
} else if (i == 2) {
cWidth = getChildAt(i).getMeasuredWidth();
} else if (i == 3) {
dWidth = getChildAt(i).getMeasuredWidth();
}
}
width = Math.max(aWidth, bWidth) + Math.max(cWidth, dWidth);//取到宽度的最大值
}
return width;
}
private int measureWidth(int heightMeasureSpec) {
int measureMode = MeasureSpec.getMode(heightMeasureSpec);
int measureSize = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if (measureMode == MeasureSpec.EXACTLY) {
height = measureSize;
} else if (measureMode == MeasureSpec.AT_MOST) {
int aHeight = 0;
int bHeight = 0;
int cHeight = 0;
int dHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
if (i == 0) {
aHeight = getChildAt(i).getMeasuredHeight();
} else if (i == 1) {
bHeight = getChildAt(i).getMeasuredHeight();
} else if (i == 2) {
cHeight = getChildAt(i).getMeasuredHeight();
} else if (i == 3) {
dHeight = getChildAt(i).getMeasuredHeight();
}
}
height = Math.max(aHeight, cHeight) + Math.max(bHeight, dHeight);//取到宽度的最大值
}
return height;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);//某一个子View
if (i == 0) {
childView.layout(0,0,childView.getMeasuredWidth(),childView.getMeasuredHeight());
} else if (i == 1) {
childView.layout(getMeasuredWidth()-childView.getMeasuredWidth(),0,getMeasuredWidth(),childView.getMeasuredHeight()+0);
} else if (i == 2) {
childView.layout(0,getMeasuredHeight()-childView.getMeasuredHeight(),childView.getMeasuredWidth(),getMeasuredHeight()+0);
} else if (i == 3) {
childView.layout(getMeasuredWidth()-childView.getMeasuredWidth(),getMeasuredHeight()-childView.getMeasuredHeight(),getMeasuredWidth(),getMeasuredHeight());
}
}
}
}
CustomViewGroup2的布局:
<com.example.user.myapplication4.CustomViewGroup2
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@android:color/holo_blue_bright" />
<TextView
android:layout_width="120dp"
android:layout_height="120dp"
android:background="@android:color/holo_blue_dark" />
<TextView
android:layout_width="140dp"
android:layout_height="150dp"
android:background="@android:color/holo_red_dark" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@android:color/holo_green_light" />
</com.example.user.myapplication4.CustomViewGroup2>
结果显示: