Android的view组件显示主要经过mesure, layout和draw这三个过程。
1、mesure阶段:
告诉手机,你这个视图的大小是多少
调用mesure(int widthSpec, int heightSpec)方法,这个方法是final不能被重写。
在这个过程里会调用
onMesure(int widthSpec, int heightSpec)方法。
2、
layout阶段:告诉手机,你这个视图的位置在屏幕的什么位置
当组件设置好大小后,调用final layout(int l, int t, int r, int b)方法进行布局
,这个方法是final不能被重写,
在这个过程里会调用
onLayout(boolean changed, int l, int t, int r, int b)方法。
3、
draw阶段:在位置和大小确定的基础上,画出视图
当布局设置好后,会调用draw(Canvas canvas)方法还绘制控件。
㈠ 先画Drawable背景
㈡ 画完背景后,draw过程会调用onDraw(Canvas canvas)方法,绘制自身。
㈢ 然后就是dispatchDraw(Canvas canvas)方法, 主要是分发给子组件进行绘制。
注意:
ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,
当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。
因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法。
在onMeasure(int, int)中,必须调用setMeasuredDimension(int width, int height)来存储测量得到的宽度和高度值,如果没有这么去做会触发异常IllegalStateException。
/**
* onMeasure传入的两个参数是由上一层控件传入的大小.
* 重写该方法时需要对计算控件的实际大小,
* 然后调用setMeasuredDimension(int, int)设置实际大小
* onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值.
* 我们需要通过:
* 用int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,
* 用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
* mode共有三种情况,取值分别为
* MeasureSpec.UNSPECIFIED : 未指定尺寸。
* 这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式
* MeasureSpec.EXACTLY : 精确尺寸。
* 当我们将控件的layout_width或layout_height指定为具体数值时
* 如:andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,
* 都是精确尺寸。
* MeasureSpec.AT_MOST : 最大尺寸。
* 当控件的layout_width或layout_height指定为WRAP_CONTENT时,
* 控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。
* 因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
Log.i("TAG","父控件宽度:"+measureWidth+"父控件高度:"+measureHeight);
measureChildren(widthMeasureSpec, heightMeasureSpec); // 测量所有子控件的宽高
// 调用setMeasuredDimension,指定视图在屏幕上的大小。屏幕上展示的大小以这个为准
setMeasuredDimension(measureWidth, measureHeight);
}
在 onLayout(boolean, int, int, int, int) 方法中,每个子视图都要调用各自的 layout(int, int, int, int) 方法。
/**
* @param changed 该参数指出当前ViewGroup的尺寸或者位置是否发生了改变
* @param l、t、r、b 当前ViewGroup相对于其父控件的坐标位置
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int mTotalHeight = 0;
int childCount = getChildCount();
// 遍历所有子视图,将每个子控件的位置坐标设好
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 获取在onMeasure中计算的视图尺寸
int measureHeight = childView.getMeasuredHeight();
int measuredWidth = childView.getMeasuredWidth();
// layout的四个参数分别是子控件 左、上、右、下。四个方位的坐标
childView.layout(0, mTotalHeight, measuredWidth, mTotalHeight + measureHeight);
mTotalHeight += measureHeight;
}
}