- View绘制流程 View的绘制基本由measure()、layout()、draw()这个三个函数完成
测量: onMeasure(): 测量自己的大小,为正式布局提供建议 布局:
onLayout(): 使用layout()函数对所有子控件布局 绘制:
onDraw(): 根据布局的位置绘图
不管是自定义View还是系统提供的TextView这些,它们都必须放置在LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这三个函数:
1.View本身大小多少,这由onMeasure()决定;
2.View在ViewGroup中的位置如何,这由onLayout()决定;
3.绘制View,onDraw()定义了如何绘制这个View。
首先先了解几个需要用到的方法:
(1)、setMeasuredDimension(width, height); 这个方法和onMeasure()方法类似。其实这个方法的作用就是 设置当前View的宽高。
(2)、 protected void Layout( int l, int t, int r, int b) {这个方法就和onLayout()方法类似了,不过少了第一个参数boolean changed这个方法的目的是用于当前ViewGroup中的子控件的布局
onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
参数即父类传过来的两个宽高的"建议值",
即把当前view的高设置为:heightMeasureSpec ;
宽设置为:widthMeasureSpec
这个参数不是简单的整数类型,而是2位整数(模式类型)和30位整数(实际数值) 的组合
在onMeasure(int, int)中,必须调用setMeasuredDimension(int width, int height)
来存储测量得到的宽度和高度值,如果没有这么去做会触发异常IllegalStateException。
也可以调用父view的onMeasure(int, int)方法。
例子:重写OnMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
} ...
setMeasuredDimension(width, height);
在ViewGroup中,给View分配的空间大小并不是确定的,有可能随着具体的变化而变化,而这个变化的条件就是传到specMode中决定的,specMode一共有三种可能:
MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的。
MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,也就是说不建议子视图的大小超过specSize中给定的值。
MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。
模式的值有:
wrap_content — MeasureSpec.AT_MOST–2
match_parent — MeasureSpec.EXACTLY-----1
具体值 — MeasureSpec.UNSPECIFIED---------0
注意:当模式是MeasureSpec.AT_MOST时,即wrap_content时,需要将大小设置一个数值。
onLayout()
onLayout方法是ViewGroup中子View的布局方法,用于放置子View的位置。放置子View很简单,只需在重写onLayout方法,然后获取子View的实例,调用子View的layout方法实现布局。在实际开发中,一般要配合onMeasure测量方法一起使用。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int top = 0;
int count = getChildCount();
for (int i=0;i<count;i++) {
View child = getChildAt(i);
int childHeight = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
//该子控件在父容器的位置 , 高度是之前所有子控件的高度和开始 ,从上往下排列,就实现了类似Linearlayout布局垂直排列的布局
child.layout(0, top, childWidth, top + childHeight); //以父容器左上角为原点进行布局
top += childHeight;
}
}
该方法在ViewGroup中定义是抽象函数,继承该类必须实现onLayout方法,而ViewGroup的onMeasure并非必须重写的。View的放置都是根据一个矩形空间放置的,onLayout传下来的l,t,r,b分别是放置父控件的矩形可用空间(除去margin和padding的空间)的左上角的left、top以及右下角right、bottom值。