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

## 二、如何画好一幅图

### 1、rotate 旋转

（1）第一个rotate函数

``````public void rotate(float degrees)

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

canvas.rotate(30);

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

（2）第二个rotate函数

``````public final void rotate(float degrees, float px, float py)

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

canvas.rotate(30, 200, 300);

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

### 2、scale 缩放

（1）第一个scale函数

``````public void scale(float sx, float 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)

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

canvas.scale(0.5f, 0.33f, 200, 300);

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

### 3、skew 斜切

``````public void skew(float sx, float sy)

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);

### 4、translate 偏移

``````public void translate(float dx, float dy)

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

canvas.translate(100, 200);

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

### 5、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);

## 三、Canvas的图形API

### 1、drawCircle 画圆

``````public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

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

### 2、drawOval 画椭圆

（1）第一个drawOval函数

``````public void drawOval(@NonNull RectF oval, @NonNull Paint paint)

``````RectF mRectF = new RectF();
mRectF.left = -150;
mRectF.top = -150;
mRectF.right = 400;
mRectF.bottom = 150;

canvas.drawOval(mRectF, mPaint);

（2）第二个drawOval函数

``````public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint)

``````canvas.drawOval(-150, -150, 400, 150, mPaint);

### 3、drawLine 画线

（1）drawLine函数

``````public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint)

``````canvas.drawLine(-200, -200,0, 0, mPaint);

（2）第一个drawLines函数

``````public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint)

``````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)

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

### 4、drawArc 画弧

（1）第一个drawArc函数

``````public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint 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)

``````canvas.drawArc(-150, -150, 400, 150, 0, 120, false, mPaint);

### 5、drawPoint 画点

（1）drawPoint函数

``````public void drawPoint(float x, float y, @NonNull Paint paint)

``````mPaint.setColor(mColor1);
mPaint.setStrokeWidth(dpToPx(5));
canvas.drawPoint(100, 100, mPaint);

``````public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint)

``````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)

``````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);

### 6、drawRect 画矩形

（1）drawRect函数

``````public void drawRect(@NonNull RectF rect, @NonNull Paint paint)
public void drawRect(@NonNull Rect r, @NonNull Paint paint)

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)

``````canvas.drawRect(-150, -150, 400, 150, mPaint);

### 7、drawRoundRect 画圆角矩形

（1）第一个drawRoundRect函数

``````public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)

``````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);

### 8、drawColor 给画布点颜色

（1）第一个drawColor函数

``````public void drawColor(@ColorInt int color)

``````canvas.drawColor(Color.parseColor("#ffffff"));

（2）第二个drawColor函数

``````public void drawColor(@ColorInt int color, @NonNull PorterDuff.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);

### 9、drawRGB 给画布点颜色

（1）drawRGB函数

``````public void drawRGB(int r, int g, int b)

``````canvas.drawARGB(255, 217, 142);

（2）drawARGB函数

``````public void drawARGB(int a, int r, int g, int b)

``````canvas.drawARGB(200, 255, 217, 142);

### 10、drawPath 绘制路径

``````public void drawPath(@NonNull Path path, @NonNull Paint paint)

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

## 四、画布保存状态API

### 1、状态值

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：保存所有信息

### 2、save

``````public int save()

### 3、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)

### 4、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)

### 5、恢复

``````// 恢复
public void restore()

// 恢复至指定的 状态栈层数
public void restoreToCount(int saveCount)

### 6、小结

``````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层出栈；

## 五、实战——时钟与指针

github地址：传送门

### 2、编程思路

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

#### （1）一个圆圈

``````canvas.drawCircle(0, 0, width / 2, mPaint);

#### （2）刻度

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

#### （3）指针

``````mPointerPath.moveTo(mPointerRadius, 0);
// 第一步
// 第二步
mPointerPath.lineTo(0, -width / 4);
// 第三步
mPointerPath.close();

### （4）开启旋转

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

... 省略创建指针

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

Android