onDraw也有个类似的方法draw方法,还是接着以前的套路,先分析一下draw方法再去具体分析onDraw方法。
draw
关于draw,源码中有很长一段注释,解释了draw到底做了什么事情。
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
简答理解一下draw执行了六个步骤:
- 绘制背景
- 如果需要,保存canvas的图层信息
- 绘制View的内容
- 如果有子View,绘制子View
- 如果需要,绘制View的边缘等(如阴影)
- 绘制 View 的装饰(如滚动)。
@CallSuper
public void draw(Canvas canvas) {
......
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
......
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
......
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
......
}
if (drawBottom) {
......
}
if (drawLeft) {
......
}
if (drawRight) {
......
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
step3:绘制内容的时候调用的View的onDraw(canvas);方法,由于View的内容各不相同所以onDraw(canvas)是一个空方法,需要子类自己去实现。
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
step4:绘制子View的时候调用dispatchDraw方法,该方法还是一个空方法需要子类自己去实现。因为当只有子View的时候才会去重写,所以看下ViewGroup是怎么实现的。
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
ViewGroup实现dispatchDraw dispatchDraw(Canvas canvas),由于ViewGroup的dispatchDraw方法内容实现比较多,直接看关键实现
protected void dispatchDraw(Canvas canvas) {
......
for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
......
}
}
遍历了所有的子 View 并调用了 ViewGroup的drawChild 方法,在看下drawChild是怎么实现的
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
最终发现还是调用的draw方法,因为ViewGroup已经帮我实现好了dispatchDraw(Canvas canvas)方法,所有我们开发中不需要自己去重写,只需要重写onDraw(Canvas canvas)方法即可。
所以draw是绘制的方法,但是具体如何去绘制子View的内容就需要重写onDraw方法,再看下onDraw方法。
由于onDraw是一个空方法,具体实现看子类,但是又个关键的参数Canvas,字面意思就是画布,绘制内容光有画布肯定是不行的,还需要画笔,所以有个关于画笔的类Paint,只有画布和画笔结合才能绘制出内容
Paint的几个最常用的方法
- Paint.setStyle(Style style) 设置绘制模式
Style | 效果 |
---|---|
Paint.Style.FILL | 填充 |
Paint.Style.FILL_AND_STROKE | 描边并填充 |
Paint.Style.STROKE | 描边 |
//设置透明度,范围为0~255
void setAlpha(int a)
//是否开启抗锯齿
void setAntiAlias(boolean aa)
//设置颜色
void setColor(int color)
//s设置颜色过滤
ColorFilter setColorFilter (ColorFilter filter)
//设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
void setDither(boolean dither)
//设置线条宽度
void setStrokeWidth(float width)
//设置文字大小
void setTextSize(float textSize)
Canvas的方法都是以draw开头的
先看先Canvas的Api
Canvas可以绘制弧线(Arc),绘制填充色(ARGB和Color),绘制Bitmap,圆(circle和oval),点(point),线(line),矩形(Rect),图片(Picture),圆角矩形 (RoundRect),文本(text),顶点(Vertices),路径(path)
在了解Canvas之前,先熟悉一下Android下的坐标系
Android中的每个View都有自己的坐标系,不会相互影响。坐标的原点在屏幕的左上角。水平方向是X轴,向右为正,向左为负。垂直方向是Y轴,向下为正,向上为负数。
void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制弧线区域
RectF rect = new RectF(0, 0, 300, 300);
canvas.drawArc(rect, //弧线所使用的矩形区域大小
0, //开始角度
300, //扫过的角度
true, //是否使用中心
paint);
}
drawArc绘制的区域是起始角度和结束角度连接起来的,其中useCenter为true时圆弧开始角度和结束角度都与中心连接。
drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//圆心坐标点X,圆心坐标点Y,半径,画笔
canvas.drawCircle(100,100,90,paint);
}
drawColor(@ColorInt int color)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);
}
绘制区域填充指定颜色,drawRGB(int r, int g, int b)也是同样的
drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
//bitmap对象,bitmap左侧的位置,bitmap顶部的位置,画笔
canvas.drawBitmap(bitmap,300,300,new Paint());
}
drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制直线
//起始点X坐标,起始点Y坐标,重点X坐标,终点Y坐标,笔画
canvas.drawLine(100,100,600,200,paint);
}
drawRect(@NonNull RectF rect, @NonNull Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//定义一个矩形区域
//左,上,右,下
RectF rect = new RectF(300,300,700,700);
//绘制矩形
canvas.drawRect(rect,paint);
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//定义一个矩形区域
//左,上,右,下
RectF rect = new RectF(300,300,700,700);
//绘制带圆角的矩形
canvas.drawRoundRect(rect,
100,//x轴的半径
100,//Y轴的半径
paint);
}
drawOval(@NonNull RectF oval, @NonNull Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//定义一个矩形区域
RectF rect = new RectF(0,0,500,200);
//绘制内切椭圆
canvas.drawOval(rect,paint);
}
drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String text = "Android";
Rect textRect = new Rect();
paint.getTextBounds(text, 0, text.length(), textRect);
//获取文字的高度
float textHeight = textRect.height();
//文字内容,文字绘制起点X,文字绘制起点Y,画笔
canvas.drawText(text, 0, 0 + textHeight, paint);
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//按照指定坐标 绘制文本内容
canvas.drawPosText("安卓", new float[]{ 90,90, 200,200 }, paint);
}
特别要注意的是文字绘制的起点是从文字的左下角开始的,实际看见文字的Y坐标需要加上文字的自身高度
void drawPath(@NonNull Path path, @NonNull Paint paint)
drawPath是绘制路径,关于Path可以参考Android开发之Path详解
- canvas.save() 将已经绘制好的图像保存起来,让后续的操作就该图层上操作
- canvas.restore()合并图层的操作,作用是将save之后绘制的图像和save之前的图像进行合并
- canvas.translate()坐标系的平移与翻转,默认绘图坐标原点在屏幕左上角。调用canvas.translate(x,y)之后将从坐标原点(0,0)移动到了(x,y)。之后操作以(x,y)作为原点执行
- canvas.rotate()将坐标系旋转了一个角度