Canvas中的绘图师讲解与实战——Android高级UI

目录
1、前言
2、如何画好一幅图
3、Canvas的图形API
4、画布保存状态API
5、实战——时钟与指针
6、写在最后
java

1、前言

在上一篇文章中,咱们只是分享了裁剪类型的API,今天接着分享绘图部分API。话很少说,老规矩,先上实战图。git

时钟与指针 github

2、如何画好一幅图

咱们在上一篇文章中讲到了,绘制一幅图的工具和坐标系。咱们继续思考,在现实中使用一张纸绘制时,咱们会对这张纸进行旋转必定角度来方便本身绘制,有时为了绘制一些细节,会进行放大,有时也会进行移动这张纸。而这些操做,在canvas中也有各自对应的操做。编程

一、rotate 旋转

(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);
复制代码

效果图

红色为原图,蓝色为旋转后绘制的图。

二、scale 缩放

(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);
复制代码

效果图

红色为原图,蓝色为缩放后绘制的图。

(2)第二个scale函数

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);
复制代码

效果图

红色为原图,蓝色为缩放后绘制的图。

三、skew 斜切

public void skew(float sx, float sy) 复制代码

描述: 进行 x 轴和 y轴 的拉伸。

拉伸规则 当一个点为(x, y)时,进行斜切变换(sx, sy),获得的结果 (rx, ry)

  1. rx = x + sx * y;
  2. ry = y + sy * x;

举个例子

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF, mPaint);

canvas.skew(1, 0.5f);

mPaint.setColor(Color.BLUE);
canvas.drawRect(mRectF, mPaint);
复制代码

效果图

红色为原图,蓝色为斜切后绘制的图。

可使用上面的 “拉伸规则” ,将红色框的点带入即可获得蓝色框对应的点。

四、translate 偏移

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);
复制代码

效果图

红色为原图,蓝色为移动后绘制的图。

五、setMatrix 矩阵

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讲解与实战实战中的使用,具体代码请进传送门

3、Canvas的图形API

一、drawCircle 画圆

public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) 复制代码

描述: 在坐标为 (cx,cy) 的地方绘制半径为 radius 的圆。

举个例子

// 在 原点处 画半径为100的圆
canvas.drawCircle(0, 0, 100, mPaint);
复制代码

效果图

二、drawOval 画椭圆

(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 的范围。

(2)第二个drawOval函数

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 中,然后一函数展现在函数参数中。

三、drawLine 画线

(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得来) 的数为止。第一条线为上面的线,第二条线为下面的线。

四、drawArc 画弧

(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);
复制代码

效果图

橘色部分则为弧线部分,紫色则为矩形范围(为了方便查看才绘出)。

(2)第二个drawArc函数

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);
复制代码

效果图

橘色为圆弧,紫色为矩形范围

五、drawPoint 画点

(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);
复制代码

效果图

(2)第一个drawPoints函数

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);
复制代码

效果图

(3)第二个drawPoints函数(带偏移)

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);
复制代码

效果图

六、drawRect 画矩形

(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 的区别:

  1. 精度不一样:RectF 四个点为浮点数,Rect 四个点为整型
  2. 所包含的方法不彻底相同。

举个例子

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);
复制代码

效果图

七、drawRoundRect 画圆角矩形

(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);
复制代码

效果图

八、drawColor 给画布点颜色

(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讲解与实战

九、drawRGB 给画布点颜色

(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);
复制代码

十、drawPath 绘制路径

public void drawPath(@NonNull Path path, @NonNull Paint paint) 复制代码

描述: 将 路径path 绘制在画布上。

举个例子 这个方法使用的地方很是之多,例如咱们绘制一个 “心” 形

mPaint.setColor(mColor1);
mPaint.setStyle(Paint.Style.FILL);
// 路径的构建,移步github
canvas.drawPath(mPath, mPaint);
复制代码

效果图

值得一提

心形路径的构建使用了 贝塞尔曲线,对 贝塞尔曲线 有兴趣的童鞋,能够移步小盆友的另外一篇博文:自带美感的贝塞尔曲线原理与实战

4、画布保存状态API

一、状态值

在进行 API 讲解前,咱们须要先说明状态值,他控制着咱们要保存什么信息。

  1. MATRIX_SAVE_FLAG:保存图层的 Matrix矩阵信息
  2. CLIP_SAVE_FLAG:保存裁剪信息
  3. HAS_ALPHA_LAYER_SAVE_FLAG:保存该图层的透明度
  4. FULL_COLOR_LAYER_SAVE_FLAG:彻底保留该图层颜色
  5. CLIP_TO_LAYER_SAVE_FLAG:建立图层时,会把canvas(全部图层)裁剪到参数指定的范围,若是省略这个flag将致使图层开销巨大,性能很差。
  6. ALL_SAVE_FLAG:保存全部信息

敲黑板了!!! 虽然罗列了这么多,但1-5的FLAG已经所有被遗弃,只剩 ALL_SAVE_FLAG 这个FLAG

二、save

public int save() 复制代码

描述: 这个函数用于保存图层状态,保存此刻的 canvas 画布的全部状态(例如:原点位置,旋转角度,一切咱们对canvas的操做都被保存)。

三、saveLayer

// 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 同样会保存进状态栈,而后经过 restorerestoreToCount 进行恢复。不一样的是该方法会建立一个新的图层

这里建立的图层,咱们能够类比为PS中的图层概念,存在乎义是不会影响到其余图层的数据。例如咱们在XFermode的博文中的刮刮卡的实战中,就有用到这一律念,不然咱们须要看到的图片也会被一同清除。

四、saveLayerAlpha

// 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 相同的是会存进状态栈和建立一个图层,而后经过 restorerestoreToCount 进行恢复。不一样的是建立的图层是具备透明度的,而透明度由 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());
}
复制代码

输出结果

咱们从代码和输出结果能够得出如下几个结论:

  1. 初始状态下,状态栈中便有一个默认的状态;
  2. 在不建立图层的状况下,全部操做都是做用于默认图层;
  3. 使用 restoreToCount(x) 进行恢复,会连同x层出栈;

一图胜千言:

将上面的代码转换成图,就以下效果

5、实战——时钟与指针

一、效果图

github地址: 传送门

二、编程思路

咱们先拆解下这幅图,其实构成的为三部分:

  1. 一个圆圈
  2. 刻度
  3. 指针

咱们逐一解决:

(1)一个圆圈

这个咱们信手拈来,canvas就有绘制圆的 API,咱们在第三节的一小点就讲到了

canvas.drawCircle(0, 0, width / 2, mPaint);
复制代码

(2)刻度

对于刻度,其实有两种画法:

  • 第一种:是听起来比较 “高大上” ,使用三角函数算出每一个刻度的起始坐标和终止坐标,而后进行绘制。
  • 第二种:较为机灵,使用咱们在 第二小节的第一点 介绍的 rotate 进行一点点的旋转画布,而后绘制线。

(3)指针

咱们须要先构建下图中蓝色的路径做为指针,由一段圆弧和两条线构成。

构建思路:

第一步:在红色的矩形内,绘制圆弧(使用了第三小节第四点) 第二步:从圆弧的左点绘制线到图中红色顶点 第三部:从红色顶点绘制线到圆弧右点,最后关闭路径path

具体代码以下:

mPointerPath.moveTo(mPointerRadius, 0);
// 第一步
mPointerPath.addArc(mPointerRectF, 0, 180);
// 第二步
mPointerPath.lineTo(0, -width / 4);
// 第三步
mPointerPath.lineTo(mPointerRadius, 0);
mPointerPath.close();
复制代码

(4)开启旋转

咱们只须要经过属性动画,让指针动起来便可。而指针的旋转只须要经过让画布旋转便可,也就是用到第二小节第一点的rotate

canvas.save();
canvas.rotate(mCurAngle);

... 省略建立指针

mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mPointerColor);
canvas.drawPath(mPointerPath, mPaint);
canvas.restore();
复制代码

时钟与指针 完整代码:传送门

6、写在最后

此次介绍的是canvas最为基础的API操做,但其实越为基础的东西,越容易被忽略也越是进阶中最须要的部分。此次写的时间耗时较久,主要是API较多,写demo和截图比较频繁。

若是你以为文章对你有所帮助,请给我一个赞并关注我吧。若是发现有那些欠妥的地方,请留言区与我讨论,咱们共同进步。

高级UI系列的Github地址:请进入传送门,若是喜欢的话给我一个star吧😄

欢迎加我微信,咱们能够进行更多更有趣的交流

相关文章
相关标签/搜索