概念:

Canvas我们可以称之为画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,非常强大。

  1. 绘制颜色 drawColor, drawRGB, drawARGB 使用单一颜色填充整个画布

    1
    canvas.drawColor(Color.BLUE); //绘制蓝色
  2. 绘制基本形状 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    canvas.drawPoint(200, 200, mPaint); //在坐标(200,200)位置绘制一个点
    canvas.drawPoints(new float[]{ //绘制一组点,坐标位置由float数组指定
    500,500,
    500,600,
    500,700
    },mPaint);
    canvas.drawLine(300,300,500,600,mPaint); // 在坐标(300,300)(500,600)之间绘制一条直线
    canvas.drawLines(new float[]{ // 绘制一组线 每四数字(两个点的坐标)确定一条线
    100,200,200,200,
    100,300,200,300
    },mPaint);
    // 第一种
    canvas.drawRect(100,100,800,400,mPaint);
    // 第二种
    Rect rect = new Rect(100,100,800,400);
    canvas.drawRect(rect,mPaint);
    // 第三种
    RectF rectF = new RectF(100,100,800,400);
    canvas.drawRect(rectF,mPaint);
    // 第一种
    RectF rectF = new RectF(100,100,800,400);
    canvas.drawRoundRect(rectF,30,30,mPaint);
    // 第二种
    canvas.drawRoundRect(100,100,800,400,30,30,mPaint);
    // 矩形
    RectF rectF = new RectF(100,100,800,400);
    // 绘制背景矩形
    mPaint.setColor(Color.GRAY);
    canvas.drawRect(rectF,mPaint);
    // 绘制圆角矩形
    mPaint.setColor(Color.BLUE);
    canvas.drawRoundRect(rectF,700,400,mPaint);
    // 第一种
    RectF rectF = new RectF(100,100,800,400);
    canvas.drawOval(rectF,mPaint);
    // 第二种
    canvas.drawOval(100,100,800,400,mPaint);
    canvas.drawCircle(500,500,400,mPaint); // 绘制一个圆心坐标在(500,500),半径为400 的圆。
    // 第一种
    //userCenter 是否使用中心
    //false 画过的是弧区
    //true 画过的是带圆心的半圆弧
    public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}
    // 第二种
    public void drawArc(float left, float top, float right, float bottom, float startAngle,
    float sweepAngle, boolean useCenter, @NonNull Paint paint) {}
  3. 绘制图片 drawBitmap, drawPicture 绘制位图和图片

  4. 绘制文本 drawText, drawPosText, drawTextOnPath 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
  5. 绘制路径 drawPath 绘制路径,绘制贝塞尔曲线时也需要用到该函数
    顶点操作 drawVertices, drawBitmapMesh 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
  6. 画布剪裁 clipPath, clipRect 设置画布的显示区域
  7. 画布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
  8. 画布变换 translate, scale, rotate, skew 依次为 位移、缩放、 旋转、错切
    Matrix(矩阵) getMatrix, setMatrix, concat 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

    创建画布

    要想绘制内容,首先需要先创建一个画笔,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 1.创建一个画笔
    private Paint mPaint = new Paint();
    // 2.初始化画笔
    private void initPaint() {
    mPaint.setColor(Color.BLACK); //设置画笔颜色
    mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
    mPaint.setStrokeWidth(10f); //设置画笔宽度为10px
    }
    // 3.在构造函数中初始化
    public SloopView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initPaint();
    }

Canvas

  1. 位移(translate)
    translate是坐标系的移动,可以为图形绘制选择一个合适的坐标系。 请注意,位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    mPaint.setColor(Color.BLACK);
    canvas.translate(200,200);
    canvas.drawCircle(0,0,100,mPaint);
    // 在坐标原点绘制一个蓝色圆形
    mPaint.setColor(Color.BLUE);
    canvas.translate(200,200);
    canvas.drawCircle(0,0,100,mPaint);
  2. 缩放(scale)
    缩放提供了两个方法,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void scale (float sx, float sy)
    public final void scale (float sx, float sy, float px, float py)
    // 将坐标系原点移动到画布正中心
    canvas.translate(mWidth / 2, mHeight / 2);
    RectF rect = new RectF(0,-400,400,0); // 矩形区域
    mPaint.setColor(Color.BLACK); // 绘制黑色矩形
    canvas.drawRect(rect,mPaint);
    canvas.scale(0.5f,0.5f); // 画布缩放
    mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
    canvas.drawRect(rect,mPaint);
  3. 旋转(rotate)
    旋转提供了两种方法:

    1
    2
    3
    public void rotate (float degrees)
    public final void rotate (float degrees, float px, float py)
    和缩放一样,第二种方法多出来的两个参数依旧是控制旋转中心点的。

默认的旋转中心依旧是坐标原点:

1
2
3
4
5
6
7
8
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.rotate(180); // 旋转180度 <-- 默认旋转中心为原点
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);

drawBitmap

通过BitmapFactory从不同位置获取Bitmap:
资源文件(drawable/mipmap/raw):

1
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.raw.bitmap);

资源文件(assets):

1
2
3
4
5
6
7
8
Bitmap bitmap=null;
try {
InputStream is = mContext.getAssets().open("bitmap.png");
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}

内存卡文件:

1
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/bitmap.png");

网络文件:

1
2
3
// 此处省略了获取网络输入流的代码
Bitmap bitmap = BitmapFactory.decodeStream(is);
is.close();

既然已经获得到了Bitmap,那么就开始本文的重点了,将Bitmap绘制到画布上。
绘制Bitmap:
依照惯例先预览一下drawBitmap的常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 第一种
public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)
// 第二种
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
// 第三种
public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
第一种方法中后两个参数(matrix, paint)是在绘制的时候对图片进行一些改变,如果只是需要将图片内容绘制出来只需要如下操作就可以了:
canvas.drawBitmap(bitmap,200,500,new Paint());
// 将画布坐标系移动到画布中央
canvas.translate(mWidth/2,mHeight/2);
// 指定图片绘制区域(左上角的四分之一)
Rect src = new Rect(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);
// 指定图片在屏幕上显示的区域
Rect dst = new Rect(0,0,200,400);
// 绘制图片
canvas.drawBitmap(bitmap,src,dst,null);

绘制文本

1
2
3
4
5
6
7
8
9
10
11
12
13
// 第一类
public void drawText (String text, float x, float y, Paint paint)
public void drawText (String text, int start, int end, float x, float y, Paint paint)
public void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
public void drawText (char[] text, int index, int count, float x, float y, Paint paint)
// 第二类
public void drawPosText (String text, float[] pos, Paint paint)
public void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
// 第三类
public void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)
public void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)

drawText()

drawText(String text, float x, float y, Paint paint)
drawText() 是 Canvas 最基本的绘制文字的方法:给出文字的内容和位置, Canvas 按要求去绘制文字。
text 是文字内容,x 和 y 是文字的坐标。但需要注意:这个坐标并不是文字的左上角,而是一个与左下角比较接近的位置。大概在这里:

参数Y 时基线(baseline)

drawTextOnPath()

沿着一条 Path 来绘制文字。这是一个耍杂技的方法。

1
2
canvas.drawPath(path, paint); // 把 Path 也绘制出来,理解起来更方便
canvas.drawTextOnPath("Hello HeCoder", path, 0, 0, paint);

StaticLayout

Canvas.drawText() 只能绘制单行的文字,而不能换行。它:

  • 不能在 View 的边缘自动折行
  • 不能在换行符 \n 处换行
    理解:

    StaticLayout 并不是一个 View 或者 ViewGroup ,而是 android.text.Layout 的子类,它是纯粹用来绘制文字的。 StaticLayout 支持换行,它既可以为文字设置宽度上限来让文字自动换行,也会在 \n 处主动换行。

    1
    2
    3
    4
    5
    6
    7
    StaticLayout 的构造方法是 StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad),其中参数里:
    width 是文字区域的宽度,文字到达这个宽度后就会自动换行;
    align 是文字的对齐方向;
    spacingmult 是行间距的倍数,通常情况下填 1 就好;
    spacingadd 是行间距的额外增加值,通常情况下填 0 就好;
    includeadd 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。

setTextSize(float textSize)

1
2
3
4
5
6
7
8
paint.setTextSize(18);
canvas.drawText(text, 100, 25, paint);
paint.setTextSize(36);
canvas.drawText(text, 100, 70, paint);
paint.setTextSize(60);
canvas.drawText(text, 100, 145, paint);
paint.setTextSize(84);
canvas.drawText(text, 100, 240, paint);

setTypeface(Typeface typeface)

1
2
3
4
5
6
paint.setTypeface(Typeface.DEFAULT);
canvas.drawText(text, 100, 150, paint);
paint.setTypeface(Typeface.SERIF);
canvas.drawText(text, 100, 300, paint);
paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "Satisfy-Regular.ttf"));
canvas.drawText(text, 100, 450, paint);

setFakeBoldText(boolean fakeBoldText)

是否使用伪粗体。

1
2
3
4
paint.setFakeBoldText(false);
canvas.drawText(text, 100, 150, paint);
paint.setFakeBoldText(true);
canvas.drawText(text, 100, 230, paint);

setStrikeThruText(boolean strikeThruText)

是否加删除线。

1
2
paint.setStrikeThruText(true);
canvas.drawText(text, 100, 150, paint);

setUnderlineText(boolean underlineText)

是否加下划线。

1
2
paint.setUnderlineText(true);
canvas.drawText(text, 100, 150, paint);

setTextSkewX(float skewX)

设置文字横向错切角度。其实就是文字倾斜度的啦。

1
2
paint.setTextSkewX(-0.5f);
canvas.drawText(text, 100, 150, paint);

setLetterSpacing(float letterSpacing)

设置字符间距。默认值是 0。

1
2
paint.setLetterSpacing(0.2f);
canvas.drawText(text, 100, 150, paint);

setTextAlign(Paint.Align align)

设置文字的对齐方式。一共有三个值:LEFT CETNER 和 RIGHT。默认值为 LEFT。

1
2
3
4
5
6
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(text, 500, 150, paint);
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, 500, 150 + textHeight, paint);
paint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(text, 500, 150 + textHeight * 2, paint);

范围裁切

Canvas.translate(float dx, float dy) 平移

1
2
anvas.clipRect(left, top, right, bottom);
canvas.drawBitmap(bitmap, x, y, paint);

Canvas.save() 和 Canvas.restore() 来及时恢复绘制范围,所以完整代码是这样的:

1
2
3
4
canvas.save();
canvas.clipRect(left, top, right, bottom);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();

几何变化

  1. Canvas.translate(float dx, float dy) 平移

    1
    2
    3
    4
    canvas.save();
    canvas.translate(200, 0);
    canvas.drawBitmap(bitmap, x, y, paint);
    canvas.restore();
  2. Canvas.rotate(float degrees, float px, float py) 旋转
    参数里的 degrees 是旋转角度,单位是度(也就是一周有 360° 的那个单位),方向是顺时针为正向; px 和 py 是轴心的位置。

    1
    2
    3
    4
    canvas.save();
    canvas.rotate(45, centerX, centerY);
    canvas.drawBitmap(bitmap, x, y, paint);
    canvas.restore();
  3. Canvas.scale(float sx, float sy, float px, float py) 放缩
    参数里的 sx sy 是横向和纵向的放缩倍数; px py 是放缩的轴心。

    1
    2
    3
    4
    canvas.save();
    canvas.scale(1.3f, 1.3f, x + bitmapWidth / 2, y + bitmapHeight / 2);
    canvas.drawBitmap(bitmap, x, y, paint);
    canvas.restore();
  4. skew(float sx, float sy) 错切
    参数里的 sx 和 sy 是 x 方向和 y 方向的错切系数。

    1
    2
    3
    4
    canvas.save();
    canvas.skew(0, 0.5f);
    canvas.drawBitmap(bitmap, x, y, paint);
    canvas.restore();

使用 Matrix 来做常见变换

Matrix 做常见变换的方式:

  1. 创建 Matrix 对象;
  2. 调用 Matrix 的 pre/postTranslate/Rotate/Scale/Skew() 方法来设置几何变换;
  3. 使用 Canvas.setMatrix(matrix) 或 Canvas.concat(matrix) 来把几何变换应用到 Canvas。

    super.onDraw(canvas);

    这个方法是空
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    / 在 View.java 的源码中,onDraw() 是空的
    // 所以直接继承 View 的类,它们的 super.onDraw() 什么也不会做
    public class View implements Drawable.Callback,
    KeyEvent.Callback, AccessibilityEventSource {
    ...
    /**
    * Implement this to do your drawing.
    *
    * @param canvas the canvas on which the background will be drawn
    */
    protected void onDraw(Canvas canvas) {
    }
    ...
    }

###