注明: 非常感谢 ​​gcssloop​​ 的博客,以下为我学习时的笔记记录。


更多Android学习笔记,请关注 ​Android-NoteBook​,欢迎Star。

一.Canvas的常用操作速查表

操作类型

相关API

备注

绘制颜色

drawColor, drawRGB, drawARGB

使用单一颜色填充整个画布

绘制基本形状

drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc

依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧

绘制图片

drawBitmap, drawPicture

绘制位图和图片

绘制文本

drawText, drawPosText, drawTextOnPath

依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字

绘制路径

drawPath

绘制路径,绘制贝塞尔曲线时也需要用到该函数

顶点操作

drawVertices, drawBitmapMesh

通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用

画布剪裁

clipPath, clipRect

设置画布的显示区域

画布快照

save, restore, saveLayerXxx, restoreToCount, getSaveCount

依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数

画布变换

translate, scale, rotate, skew

依次为 位移、缩放、 旋转、错切

Matrix(矩阵)

getMatrix, setMatrix, concat

实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

Canvas基本操作

绘制图片

绘制有两种方法,drawPicture(矢量图)和drawBitmap(位图);

drawPicture

主要作用就如同录制一样,可以吧 Canvas 绘制点,线,矩形等诸多操作用 Picture录制下来,下次需要的时候拿来就能用,使用 Picture相比 再次调用 绘图API,开销是比较小的,也就是说对于重复的操作可以更加快捷。

注:Picture如同一个录制Canvas的录像机。

相关的方法:

相关方法

简介

public int getWidth ()

获取宽度

public int getHeight ()

获取高度

public Canvas beginRecording (int width, int height)

开始录制 (返回一个Canvas,在Canvas中所有的绘制都会存储在Picture中)

public void endRecording ()

结束录制

public void draw (Canvas canvas)

将Picture中内容绘制到Canvas中

public static Picture createFromStream (InputStream stream)

(已废弃)通过输入流创建一个Picture

public void writeToStream (OutputStream stream)

(已废弃)将Picture中内容写出到输出流中

使用Demo

准备工作:

录制内容,即将一些Canvas 操作用 Picture存储起来,录制的内容是不会直接显示在屏幕上的,只是存储起来了而已。

private Picture picture=new Picture();
public CanvasView(Context context) {
super(context);
}

/**
* 使用前调用
* @param context
* @param attrs
*/
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
//调用录制
recording();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
private void recording(){
//开始录制(接受返回值 Canvas)
Canvas canvas=picture.beginRecording(500,500);
Paint paint=new Paint();
paint.setColor(Color.BLACK); //设置画笔颜色
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10f); //设置画笔宽度为10px

//在Canvas中具体操作
//位移
canvas.translate(250,250);
//绘制一个圆
canvas.drawCircle(0,0,100,paint);

picture.endRecording();
}

具体使用

Picture虽然方法不多,但是具体使用起来还是分以下几种情况,

序号

简介

1

使用Picture提供的draw方法绘制。

2

使用Canvas提供的drawPicture方法绘制。

3

将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。

以上几种方法的主要区别:

主要区别

分类

简介

是否对Canvas有影响

1有影响 2,3不影响

此处指绘制完成后是否会影响Canvas的状态(Matrix clip等)

可操作性强弱

1可操作性较弱 2,3可操作性较强

此处的可操作性可以简单理解为对绘制结果可控程度。

使用Picture提供的 draw方法绘制

//将Picture中的内容绘制在Canvas上
picture.draw(canvas);

注意:这种方法在比较低系统上绘制后可能会影响 Canvas 状态,所以这种方法一般不会使用。

使用 Canvas 提供的 drawPicture 方法监测

drawPicture有三种方法

public void drawPicture(Picture picture)
public void drawPicture(Picture picture,Rest dst)
public void drawPicture(Picture picture,RectF dst)

和使用 Picture 的draw 方法不同,Canvas 的 drawPicture 不会影响 Canvas状态。

//这里的 drawPicture 是根据绘制缩放
canvas.drawPicture(picture,new RectF(0,0,picture.getWidth(),200));

对照上面的图片,可以比较明显的看出,绘制的内容根据选区进行了缩放。

将Picture 包装成为 PictureDrawable ,使用PictureDrawable 的 draw 方法绘制

//将Picture中的内容绘制在Canvas上
PictureDrawable drawable=new PictureDrawable(picture);
//设置绘制区域--- 此处的绘制实际内容不会缩放
drawable.setBounds(0,0,250,picture.getHeight());
//绘制
drawable.draw(canvas);

注意: 此处setBounds 是设置在画布上的绘制区别,并非根据该区域进行缩放,也不是裁剪Picture,每次都从Picture的左上角开始绘制


drawBitmap

首先要绘制 Bitmap,就先要获取一个Bitmap,那么该如何获取呢?

获取Bitmap方式

序号

获取方式

备注

1

通过Bitmap创建

复制一个已有的Bitmap(新Bitmap状态和原有的一致) 或者 创建一个空白的Bitmap(内容可改变)

2

通过BitmapDrawable获取

从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap

3

通过BitmapFactory获取

从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap

通过BitmapFactory从不同位置获取Bitmap:

资源文件(drawable/mipmap/raw):

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

资源文件(assets):

Bitmap bitmap=null;
try {
InputStream is = mContext.getAssets().open("bitmap.png");
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}

内存卡文件:

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

网络文件:

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

既然已经获得到了Bitmap,那么就开始本文的重点了,将Bitmap绘制到画布上。

绘制Bitmap:

先看一下用法:

// 第一种
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) 是在绘制的时候对图片进行一些改变,如果只是需要将图片内容绘制出来只需要如下操作即可。

注意: 图片左上角位置默认为坐标原点。

Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.test);
//默认方式
canvas.drawBitmap(bitmap,new Matrix(),new Paint());

第二种方法就是在绘制时指定图片左上角的坐标(距离坐标原点的距离)。


注意:此处指定的是与坐标原点的距离,并非是与屏幕顶部和左侧的距离,虽然默认状态下两者是重合的,但是也需要注意分别两者的不同。


Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.test);
//设置原点位置
canvas.drawBitmap(bitmap,200,500,new Paint());

第三种方法,上面多了两个矩形区域(src,dst),这两个矩形选区的作用如下:

名称

作用

Rect src

指定绘制图片的区域

Rect dst 或RectF dst

指定图片在屏幕上显示(绘制)的区域

Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.test);
//将画布坐标系移动到画布中央
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);

下面来做一个练习Demo

图片如下

Android自定义View-Canvas之图片文字(学习笔记)_自定义View

/**
* @author Petterp on 2019/6/27
* Summary:一个简单的圆加监听是否在圆内
* 不适合项目使用,简单demo
* 邮箱:1509492795@qq.com
*/
public class CanvasView extends View {
private int mWidth;
private int mHeight;
private Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ok);
private Paint paint;

public CanvasView(Context context) {
super(context);
}

//中间值,图片总页13页
private int mode = 0;

/**
* 使用前调用
*
* @param context
* @param attrs
*/
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
startAnimator();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}

private void initPaint() {
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.YELLOW);
//抗锯齿
paint.setAntiAlias(true);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_DOWN:
//获取屏幕上点击的坐标
float x = event.getX();
float y = event.getY();

//用勾股定理判手指按下时三角形的斜线是否小于等于半径即可
//点击位置x坐标与圆心的x坐标的距离
int distanceX = (int) Math.abs(x - mWidth / 2);
//点击位置y坐标与圆心的y坐标的距离
int distanceY = (int) Math.abs(y - mHeight / 2);
//点击位置与圆心的直线距离
int distanceZ = (int) Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
//如果点击位置与圆心的距离大于圆的半径,证明点击位置没有在圆内
if (distanceZ <= 220) {
startAnimator();
}
//如果坐标在我们文字域内,则调用动画重新开始
break;

}
return super.onTouchEvent(event);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("demo", "执行");
//将画布坐标系移动到画布中央
canvas.translate(mWidth / 2, mHeight / 2);
//绘制圆
canvas.drawCircle(0, 0, 220, paint);
//指定图片绘制区域
Rect src = new Rect((bitmap.getWidth() / 13 * (mode - 1)), 0, (bitmap.getWidth() / 13 * mode), bitmap.getHeight());
//指定图片在屏幕上显示的区域
Rect dst = new Rect(-200, -200, 200, 200);
//绘制图片
canvas.drawBitmap(bitmap, src, dst, null);
}

private void startAnimator() {
ValueAnimator animator = ValueAnimator.ofInt(0, 13);
animator.setDuration(500);
animator.addUpdateListener(animation -> {
//获取变动后的值
mode = (int) animation.getAnimatedValue();
Log.e("Demo", "数据" + mode);
//刷新ui
invalidate();
});
animator.start();
}
}


绘制文字

常用方法

// 第一类
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)

CharSequence是一个接口,表示有序的字符集合。

String,StringBuilder,StringBuffer都实现了这个接口。

CharSequence

接口,表示有序的字符集合

String

常量,不可变

StringBuffer

可变长度字符序列,线程安全

StringBuilder

可变长度字符序列,非线程安全

绘制文字部分大致可以分为3类:


  1. 第一类只能指定文本基线位置(基线x默认在字符串左侧,基线y默认在字符串下方)
  2. 第二类可以分别指定每个文字的位置。
  3. 第三类是指定一个路径,根据路径绘制文字。

Paint文本相关常用方法表

标题

相关方法

备注

色彩

setColor setARGB setAlpha

设置颜色,透明度

大小

setTextSize

设置文本字体大小

字体

setTypeface

设置或清除字体样式

样式

setStyle

填充(FILL),描边(STROKE),填充加描边(FILL_AND_STROKE)

对齐

setTextAlign

左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT)

测量

measureText

测量文本大小(注意,请在设置完文本各项参数后调用)

第一类(drawText)

Paint paint=new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(50);
canvas.drawText("123",200,500,paint);

除了可以指定绘制文本的起始位置,还可以读取文本中的一部分内容进行绘制:

不过采用的规则是前闭后开,也就是 [ ) 这样;

如下Dmeo

String res="Pettterp";
canvas.drawText(res,1,4,200,500,paint);
char[] chars=res.toCharArray();
canvas.drawText(chars,0,4,200,600,paint);

用的是字符串+char数组,不过它们的用法都是相同的。都是前闭后开,也没什么难的。


第二类 (drawPosText)->已废弃

第二类中没有指定x,y坐标的参数,取而代之的是添加了一个 float[] pos的数组,这个数组的作用是用来指定坐标。当然出来的效果就是每个字符都会指定一个位置。

String res="Pet";
canvas.drawPosText(res,new float[]{
100,100,
200,200,
300,300
},paint);

效果如上,不过有缺点就是每一个字符都必须指定位置,而且字符很多的时候浪费内存。不过这个方法现在已经被废弃了。