目录
1、前言
2、如何画好一幅图
3、Canvas的图形API
4、画布保存状态API
5、实战——时钟与指针
6、写在最后
java
在上一篇文章中,咱们只是分享了裁剪类型的API,今天接着分享绘图部分API。话很少说,老规矩,先上实战图。git
时钟与指针 github
咱们在上一篇文章中讲到了,绘制一幅图的工具和坐标系。咱们继续思考,在现实中使用一张纸绘制时,咱们会对这张纸进行旋转必定角度来方便本身绘制,有时为了绘制一些细节,会进行放大,有时也会进行移动这张纸。而这些操做,在canvas中也有各自对应的操做。编程
(1)第一个rotate函数canvas
public void rotate(float degrees) 复制代码
描述: 以原点为旋转中心,旋转画布 degrees 角度。正数为顺时针旋转,负数为逆时针旋转。数组
举个例子微信
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
canvas.rotate(30);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图svg
红色为原图,蓝色为旋转后绘制的图。 函数
(2)第二个rotate函数工具
public final void rotate(float degrees, float px, float py) 复制代码
描述: 以 (px, py) 为旋转中心,将画布旋转 degrees 角度。正数为顺时针旋转,负数为逆时针旋转。
举个例子
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
canvas.rotate(30, 200, 300);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
红色为原图,蓝色为旋转后绘制的图。
(1)第一个scale函数
public void scale(float sx, float sy) 复制代码
描述 : 以原点进行缩放画布,x轴缩放 sx 倍,y轴缩放 sy 倍。
举个例子
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
canvas.scale(0.5f,0.33f);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
红色为原图,蓝色为缩放后绘制的图。
public final void scale(float sx, float sy, float px, float py) 复制代码
描述: 以 (px, py) 进行缩放画布,x轴缩放 sx 倍,y轴缩放 sy 倍。
举个例子
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
canvas.scale(0.5f, 0.33f, 200, 300);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
红色为原图,蓝色为缩放后绘制的图。
public void skew(float sx, float sy) 复制代码
描述: 进行 x 轴和 y轴 的拉伸。
拉伸规则 当一个点为(x, y)时,进行斜切变换(sx, sy),获得的结果 (rx, ry)
举个例子
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
canvas.skew(1, 0.5f);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
红色为原图,蓝色为斜切后绘制的图。
可使用上面的 “拉伸规则” ,将红色框的点带入即可获得蓝色框对应的点。
public void translate(float dx, float dy) 复制代码
描述: 将画布水平移动 dx 个像素, 垂直移动 dy 个像素。
举个例子
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
canvas.translate(100, 200);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
红色为原图,蓝色为移动后绘制的图。
public void setMatrix(@Nullable Matrix matrix) 复制代码
描述: 将矩阵做用于画布。
举个例子
mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);
mMatrix.preTranslate(getWidth() / 2, getHeight() / 2);
mMatrix.preScale(2, 1);
canvas.setMatrix(mMatrix);
mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
红色为原图,蓝色为使用矩阵后绘制的图。
矩阵的内容比较多,这里只是略带一提,若是想见识见识他的真正威力,能够看看在小盆友另外一篇博文放荡不羁SVG讲解与实战实战中的使用,具体代码请进传送门。
public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) 复制代码
描述: 在坐标为 (cx,cy) 的地方绘制半径为 radius 的圆。
举个例子
// 在 原点处 画半径为100的圆
canvas.drawCircle(0, 0, 100, mPaint);
复制代码
效果图
(1)第一个drawOval函数
public void drawOval(@NonNull RectF oval, @NonNull Paint paint) 复制代码
描述: 在 oval 的矩形范围内,绘制椭圆。
举个例子
RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;
canvas.drawOval(mRectF, mPaint);
复制代码
效果图
橘色部分则为咱们绘制的椭圆,而紫色框(为了方便观看而绘制出来)则是咱们的 oval 的范围。
public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) 复制代码
描述: 在 左上(left,top) 和 右下(right,bottom) 造成的矩形范围内,绘制椭圆。
值得注意的是,这个方法只能在 API21 以上的版本 才能使用,因此建议使用第一个函数。
举个例子
canvas.drawOval(-150, -150, 400, 150, mPaint);
复制代码
效果图
橘色部分则为咱们绘制的椭圆,而紫色框(为了方便观看而绘制出来)则是咱们的 oval 的范围。
两个函数效果彻底同样,只是前一个函数将两个坐标点封装在 Rect 中,然后一函数展现在函数参数中。
(1)drawLine函数
public void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) 复制代码
描述: 在坐标 (startX, startY) 和 (stopX, stopY) 中绘制一条直线。
举个例子
canvas.drawLine(-200, -200,0, 0, mPaint);
复制代码
效果图
(2)第一个drawLines函数
public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) 复制代码
描述: pts数组中每四个数构成一条直线,每四个数中前两个为起始坐标,后两个为终止坐标。若是不够四个数,则这一组不进行绘制。
举个例子
private float[] pts = new float[]{
0, -400, 200, -400, // 构成上面的线
-300, 0, -300, 300, // 构成左边的线
0, 400, 300, 400 // 构成右边的线
};
canvas.drawLines(pts, mPaint);
复制代码
效果图
(3)第二个drawLines函数(带偏移)
public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count, @NonNull Paint paint) 复制代码
描述: 该方法比上一个方法多加两个参数,即偏移量和数量。偏移量offset为一时,则从pts的下标为1的地方开始进行读数,count则决定了多少个数。
举个例子
private float[] pts = new float[]{
0, -400, 200, -400,
-300, 0, -300, 300,
0, 400, 300, 400
};
canvas.drawLines(pts, 2, 8, mPaint);
复制代码
效果图
pts数组中,从下标为2的数字开始,每四个数构成一条线,直到下标为 10 (由8+2得来) 的数为止。第一条线为上面的线,第二条线为下面的线。
(1)第一个drawArc函数
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) 复制代码
描述: 在 oval矩形范围内,绘制 从startAngle角度 到 sweepAngle角度的圆弧。
参数说明: 1)oval:圆弧所绘的矩形范围区域。 2)startAngle:起始角度。0度时,指向为坐标系中的x轴正半轴。 3)sweepAngle:基于 startAngle 角度,扫过的角度范围,正数则按顺时针方向,负数则按逆时针方向。 4)useCenter:弧的两端是否要链接中心点。true链接中心点,false不链接中心点。 5)paint:画笔。
举个例子
RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;
canvas.drawArc(mRectF, 0, 120, true, mPaint);
复制代码
效果图
橘色部分则为弧线部分,紫色则为矩形范围(为了方便查看才绘出)。
public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) 复制代码
描述: 该方法和上一方法功能彻底同样,只是用四个 float 表示 矩形的端点。
举个例子
canvas.drawArc(-150, -150, 400, 150, 0, 120, false, mPaint);
复制代码
效果图
橘色为圆弧,紫色为矩形范围
(1)drawPoint函数
public void drawPoint(float x, float y, @NonNull Paint paint) 复制代码
描述: 在坐标为 (x,y) 处绘制点
举个例子
mPaint.setColor(mColor1);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoint(100, 100, mPaint);
复制代码
效果图
public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) 复制代码
描述: pts数组中每两个数构成一个坐标(前者为x,后者为y),并在该坐标处点。
举个例子
private float[] pts = new float[]{
0, -400,
200, -400,
-300, 0
};
mPaint.setColor(mColor2);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoints(pts, mPaint);
复制代码
效果图
public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count, @NonNull Paint paint) 复制代码
描述: 这个方法和上面的方法大体相同,惟一区别在于从下标为offset开始读取坐标,读取长度个数为count。
举个例子
private float[] pts = new float[]{
0, -400,
200, -400,
-300, 0
};
mPaint.setColor(mColor2);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoints(pts, 1, pts.length - 1, mPaint);
复制代码
效果图
(1)drawRect函数
public void drawRect(@NonNull RectF rect, @NonNull Paint paint) public void drawRect(@NonNull Rect r, @NonNull Paint paint) 复制代码
描述: 在 rect 的范围内绘制矩形,两个方法的惟一区别在于第一个参数类型分别为 RectF 和 Rect。
RectF 和 Rect 的区别:
举个例子
RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;
canvas.drawRect(mRectF, mPaint);
复制代码
效果图
(2)drawRect函数
public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) 复制代码
描述: 在 (left,top) 和 (right,bottom) 造成的矩形范围内绘制矩形。
举个例子
canvas.drawRect(-150, -150, 400, 150, mPaint);
复制代码
效果图
(1)第一个drawRoundRect函数
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) 复制代码
描述: 在 rect 范围内,绘制圆角矩形。
参数说明: 1)rx:水平方向的半径,下图中的橘色部分 2)ry:竖直方向的半径,下图中的红色部分
canvas.drawRoundRect(mRectF, 80, 100, mPaint);
复制代码
效果图
(2)第二个drawRoundRect函数
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint) 复制代码
描述: 与上述的方法功能彻底相同,只是绘制范围由四个浮点数进行肯定。
举个例子
canvas.drawRoundRect(-150, -150, 400, 150, 100, 50, mPaint);
复制代码
效果图
(1)第一个drawColor函数
public void drawColor(@ColorInt int color) 复制代码
描述: 给画布绘制color颜色值。
举个例子
canvas.drawColor(Color.parseColor("#ffffff"));
复制代码
比较简单就不上效果图了。
(2)第二个drawColor函数
public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) 复制代码
描述: 给画布绘制颜色,会与以前的图形有 mode 的做用。
举个例子
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.logo);
Matrix mMatrix = new Matrix();
mMatrix.setScale(0.25f, 0.25f);
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
canvas.drawColor(Color.parseColor("#88880000"),
PorterDuff.Mode.DST_OVER);
复制代码
效果图
咱们所介绍的第一个 drawColor(@ColorInt int color)
函数,其实最后使用了 PorterDuff.Mode.SRC_OVER
模式。
至于 PorterDuff.Mode 的具体使用,请看小盆友的另外一篇博文:图像操纵大师Xfermode讲解与实战
(1)drawRGB函数
public void drawRGB(int r, int g, int b) 复制代码
描述: 给画布绘制颜色,按照 红(r),绿(g),蓝(b) 三原色进行组合
举个例子
canvas.drawARGB(255, 217, 142);
复制代码
(2)drawARGB函数
public void drawARGB(int a, int r, int g, int b) 复制代码
描述: 给画布绘制颜色,按照 透明度(a),红(r),绿(g),蓝(b) 三原色进行组合
举个例子
canvas.drawARGB(200, 255, 217, 142);
复制代码
public void drawPath(@NonNull Path path, @NonNull Paint paint) 复制代码
描述: 将 路径path 绘制在画布上。
举个例子 这个方法使用的地方很是之多,例如咱们绘制一个 “心” 形
mPaint.setColor(mColor1);
mPaint.setStyle(Paint.Style.FILL);
// 路径的构建,移步github
canvas.drawPath(mPath, mPaint);
复制代码
效果图
心形路径的构建使用了 贝塞尔曲线,对 贝塞尔曲线 有兴趣的童鞋,能够移步小盆友的另外一篇博文:自带美感的贝塞尔曲线原理与实战
在进行 API 讲解前,咱们须要先说明状态值,他控制着咱们要保存什么信息。
敲黑板了!!! 虽然罗列了这么多,但1-5的FLAG已经所有被遗弃,只剩 ALL_SAVE_FLAG
这个FLAG。
public int save() 复制代码
描述: 这个函数用于保存图层状态,保存此刻的 canvas 画布的全部状态(例如:原点位置,旋转角度,一切咱们对canvas的操做都被保存)。
// saveFlags 只能是 Canvas.ALL_SAVE_FLAG
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint, @Saveflags int saveFlags) // saveFlags 只能是 Canvas.ALL_SAVE_FLAG public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) // API21及以上才可以使用 public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) // API21及以上才可以使用 public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) 复制代码
描述: 该方法与 save
同样会保存进状态栈,而后经过 restore
或 restoreToCount
进行恢复。不一样的是该方法会建立一个新的图层。
这里建立的图层,咱们能够类比为PS中的图层概念,存在乎义是不会影响到其余图层的数据。例如咱们在XFermode的博文中的刮刮卡的实战中,就有用到这一律念,不然咱们须要看到的图片也会被一同清除。
// saveFlags 只能是 Canvas.ALL_SAVE_FLAG
public int saveLayerAlpha(@Nullable RectF bounds, int alpha, @Saveflags int saveFlags) // saveFlags 只能是 Canvas.ALL_SAVE_FLAG public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, @Saveflags int saveFlags) // API21及以上才可以使用 public int saveLayerAlpha(@Nullable RectF bounds, int alpha) // API21及以上才可以使用 public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) 复制代码
描述: 与 saveLayer
相同的是会存进状态栈和建立一个图层,而后经过 restore
或 restoreToCount
进行恢复。不一样的是建立的图层是具备透明度的,而透明度由 alpha 决定,范围为 0-255。
// 恢复
public void restore() // 恢复至指定的 状态栈层数 public void restoreToCount(int saveCount) 复制代码
描述: 这两个方法,是将上面三种方法保存的函数进行恢复。而区别在于 restore
每次从状态栈中恢复拿出一个状态恢复,而 restoreToCount
是 恢复到指定的状态栈层数(该层也会被出栈),这个 saveCount 参数在上面三种类型的方法调用后都会进行返回各自对应的层数。
先举个例子汇总一下这几个方法:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
log(canvas);
int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(),
mPaint, Canvas.ALL_SAVE_FLAG);
log(canvas);
canvas.save();
log(canvas);
canvas.saveLayer(0, 0, getWidth(), getHeight(),
mPaint, Canvas.ALL_SAVE_FLAG);
log(canvas);
canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(),
50, Canvas.ALL_SAVE_FLAG);
log(canvas);
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.drawRect(mRect, mPaint);
canvas.restore();
log(canvas);
canvas.restoreToCount(layer);
log(canvas);
}
private void log(Canvas canvas) {
Log.i("canvas", "canvas count:" + canvas.getSaveCount());
}
复制代码
输出结果
restoreToCount(x)
进行恢复,会连同x层出栈;一图胜千言:
将上面的代码转换成图,就以下效果
咱们先拆解下这幅图,其实构成的为三部分:
咱们逐一解决:
这个咱们信手拈来,canvas就有绘制圆的 API,咱们在第三节的一小点就讲到了
canvas.drawCircle(0, 0, width / 2, mPaint);
复制代码
对于刻度,其实有两种画法:
rotate
进行一点点的旋转画布,而后绘制线。咱们须要先构建下图中蓝色的路径做为指针,由一段圆弧和两条线构成。
第一步:在红色的矩形内,绘制圆弧(使用了第三小节第四点) 第二步:从圆弧的左点绘制线到图中红色顶点 第三部:从红色顶点绘制线到圆弧右点,最后关闭路径path
具体代码以下:
mPointerPath.moveTo(mPointerRadius, 0);
// 第一步
mPointerPath.addArc(mPointerRectF, 0, 180);
// 第二步
mPointerPath.lineTo(0, -width / 4);
// 第三步
mPointerPath.lineTo(mPointerRadius, 0);
mPointerPath.close();
复制代码
咱们只须要经过属性动画,让指针动起来便可。而指针的旋转只须要经过让画布旋转便可,也就是用到第二小节第一点的rotate
。
canvas.save();
canvas.rotate(mCurAngle);
... 省略建立指针
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mPointerColor);
canvas.drawPath(mPointerPath, mPaint);
canvas.restore();
复制代码
时钟与指针 完整代码:传送门
此次介绍的是canvas最为基础的API操做,但其实越为基础的东西,越容易被忽略也越是进阶中最须要的部分。此次写的时间耗时较久,主要是API较多,写demo和截图比较频繁。
若是你以为文章对你有所帮助,请给我一个赞并关注我吧。若是发现有那些欠妥的地方,请留言区与我讨论,咱们共同进步。
高级UI系列的Github地址:请进入传送门,若是喜欢的话给我一个star吧😄
欢迎加我微信,咱们能够进行更多更有趣的交流