View的测量:
通过MeasureSpec这一个类, 就可以获取View的测量模式和View想要绘制的大小。 MeasureSpec类, 是一个32位的int值,前两位为测量的模式,测量的模式有三种(EXACTLY, AT_MOST, UNSPECIFIED)
View类默认的测量view方式为onMeasure() 且只支持EXACTLY 模式, 所以如果在自定义控件的时候不重写onMeasure()方法的话, 就只能使用EXACTAL 模式。控件可以响应你指定的具体高度值或者是match_parent 属性。而如果要让自定义View 支持wrap_content 属性,那么必须重写onMeasure() 来指定wrap_content时的大小。
通过MeasureSpec类可以获得View的测量模式与和View想测量的大小, 有了这些信息我们就可以控制view最后显示的大小。
public class TeachingView extends View {
public TeachingView(Context context) {
super(context);
}
public TeachingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TeachingView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
//调用自动定义的measureWidth() 和 measureHeight() 对宽高进行重写定义。
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
int width = getWidth();
int height = getHeight();
Log.d("yy", "width : " + width + " height : " + height);
}
}
第一种条件,宽高值都为400dp, 运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/be3bd7e8f0ad1ae97ef38da4a9e6592f.png)
第二种条件:指定宽高属性为 match_parent, 运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/ce7e5b48b863abdeabf3d8464d0a3cab.png)
第三种条件:指定宽高为wrap_content 属性, 此时如果不重写onMeasure() 方法,那么系统就不知道该使用默认多大的尺寸。 因此会默认填充父布局, 所以需要重写onMeasure() 方法 指定wrap_content 属性下的默认大小, 运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/1b9980ef028dee43f065bc8df851f47b.png)
View的绘制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
int width = getWidth();
int height = getHeight();
Log.d("yy", "width : " + width + " height : " + height);
}
完成view的测量后, 重写onDraw() 方法,此方法中的参数即为Canvas, 并在Canvas对象上绘制图像 Canvas对象时2D绘图必须使用到的API。在onDraw()中使用这个对象就可以进行绘图, 但在其他地方, 通常需要使用代码创建一个Canvas对象:
Canvas canvas = new Canvas(bitmap);
这个过程叫做装载画布, 这个bitmap用来存储所有绘制在Canvas上的像素信息。当通过这种方式创建了Canvas对象后, 后面所有的Canvas.DrawXXXX方法都发生在这个bitmap上。如果在View类的onDraw() 方法中通过下面的代码,可以了解到Canvas 和 Bitmap直接的关系。 首先在onDraw() 方法绘制两个bitmap
canvas.drawBitmap(bitmap1, 0, 0, null);
canvas.drawBitmap(bitmap2, 0, 0, null);
对于bitmap2 将它装载到另一个对象中:
Canvas mCanvas = new Canvas(bitmap2);
那么在mCanvas上绘制view时:
mCanvas.drawxxx
通过mCanvas将绘制的效果作用在了bitmap2上, 这是因为bitmap2承载了在mCanvas上进行的绘图操作,没有将图形直接绘制在onDraw() 方法指定的画布上, 而是通过改变bitmap, 使得view 进行的重绘, 从而显示改变之后的bitmap。通过改变bitmap,然后让view重绘, 从而显示改变后的bitmap 虽然使用了Canvas 的绘图API, 但其实并没有将图形直接绘制在onDraw() 方法指定的那块画布上。
--------------------------------------------------------------------------------------------------------------