Canvas类的最全面详解
来源:互联网 发布:windows rm 命令 编辑:程序博客网 时间:2024/06/09 20:09
前言
- 自定义View是Android开发者必须了解的基础;而Canvas类的使用在自定义View绘制中发挥着非常重要的作用
- 网上有大量关于自定义View中Canvas类的文章,但存在一些问题:内容不全、思路不清晰、简单问题复杂化等等
- 今天,我将全面总结自定义View中的Canvas类的使用,我能保证这是市面上的最全面、最清晰、最易懂的
- 文章较长,建议收藏等充足时间再进行阅读
- 阅读本文前请先阅读 自定义View基础 - 最易懂的自定义View原理系列
目录
1. 简介
- 定义:画布,是一种绘制时的规则
是安卓平台2D图形绘制的基础
- 作用:规定绘制内容时的规则 & 内容
1. 记住:绘制内容是根据画布的规定绘制在屏幕上的
2. 理解为:画布只是绘制时的规则,但内容实际上是绘制在屏幕上的
2. Canvas的本质
请务必记住:
- 绘制内容是根据画布(Canvas)的规定绘制在屏幕上的
- 画布(Canvas)只是绘制时的规则,但内容实际上是绘制在屏幕上的
为了更好地说明绘制内容的本质和Canvas,请看下面例子:
2.1 实例
- 实例情况:先画一个矩形(蓝色);然后移动画布;再画一个矩形(红色)
- 代码分析:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 效果图
- 具体流程分析
看完上述分析,你应该非常明白Canvas的本质了。
- 总结
绘制内容是根据画布的规定绘制在屏幕上的- 内容实际上是绘制在屏幕上;
- 画布,即Canvas,只是规定了绘制内容时的规则;
- 内容的位置由坐标决定,而坐标是相对于画布而言的
- 内容实际上是绘制在屏幕上;
注:关于对画布的操作(缩放、旋转和错切)原理都是相同的,下面会详细说明。
3. 基础
3.1 Paint类
- 定义:画笔
- 作用:确定绘制内容的具体效果(如颜色、大小等等)
在绘制内容时需要画笔Paint
- 具体使用:
步骤1:创建一个画笔对象
步骤2:画笔设置,即设置绘制内容的具体效果(如颜色、大小等等)
步骤3:初始化画笔(尽量选择在View的构造函数) 具体使用如下:
- 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
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
Style模式效果如下:
3.2 Path类
具体请看我写的另外一篇文章:Path类的最全面详解 - 自定义View应用系列
3.3 关闭硬件加速
- 在Android4.0的设备上,在打开硬件加速的情况下,使用自定义View可能会出现问题
具体问题可以看这里。
- 所以测试前,请先关闭硬件加速。具体关闭方式如下:
在AndroidMenifest.xml的application节点添加:
- 1
4. Canvas的使用
4.1 对象创建 & 获取
Canvas对象 & 获取的方法有4个:- 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
官方推荐方法4来创建并获取Canvas,原因:
- SurfaceView里有一条线程是专门用于画图,所以方法4的画图性能最好,并适用于高质量的、刷新频率高的图形
- 而方法3刷新频率低于方法3,但系统花销小,节省资源
4.2 绘制方法使用
- 利用Canvas类可绘画出很多内容,如图形、文字、线条等等;
- 对应使用的方法如下:
仅列出常用方法,更加详细的方法可参考官方文档 Canvas
下面我将逐个方法进行详细讲解
特别注意
Canvas具体使用时是在复写的onDraw()里:- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
具体为什么,请看我写的自定义View原理系列文章:
自定义View Measure过程 - 最易懂的自定义View原理系列(2)
自定义View Layout过程 - 最易懂的自定义View原理系列(3)
自定义View Draw过程- 最易懂的自定义View原理系列(4)
4.2.1 绘制颜色
- 作用:将颜色填充整个画布,常用于绘制底色
- 具体使用
- 1
- 2
- 3
4.2.2 绘制基本图形
a. 绘制点(drawPoint)
原理:在某个坐标处绘制点
可画一个点或一组点(多个点)
具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
b. 绘制直线(drawLine)
- 原理:两点(初始点 & 结束点)确定一条直线
- 具体使用:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
c. 绘制矩形(drawRect)
原理:矩形的对角线顶点确定一个矩形
一般是采用左上角和右下角的两个点的坐标。
具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
d. 绘制圆角矩形
原理:矩形的对角线顶点确定一个矩形
类似于绘制矩形
具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 与矩形相比,圆角矩形多了两个参数rx 和 ry
- 圆角矩形的角是椭圆的圆弧,rx 和 ry实际上是椭圆的两个半径,如下图:
- 特别注意:当 rx大于宽度的一半, ry大于高度一半 时,画出来的为椭圆
实际上,在rx为宽度的一半,ry为高度的一半时,刚好是一个椭圆;但由于当rx大于宽度一半,ry大于高度一半时,无法计算出圆弧,所以drawRoundRect对大于该数值的参数进行了修正,凡是大于一半的参数均按照一半来处理
e. 绘制椭圆
原理:矩形的对角线顶点确定矩形,根据传入矩形的长宽作为长轴和短轴画椭圆
- 椭圆传入的参数和矩形是一样的;
- 绘制椭圆实际上是绘制一个矩形的内切图形。
具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
f. 绘制圆
- 原理:圆心坐标+半径决定圆
- 具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
g. 绘制圆弧
- 原理:通过圆弧角度的起始位置和扫过的角度确定圆弧
- 具体使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
为了理解第三个参数:useCenter
,看以下示例:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
从示例可以发现:
- 不使用中心点:圆弧的形状 = (起、止点连线+圆弧)构成的面积
- 使用中心店:圆弧面积 = (起点、圆心连线 + 止点、圆心连线+圆弧)构成的面积
类似扇形
4.2.3 绘制文字
绘制文字分为三种应用场景:
情况1:指定文本开始的位置
- 即指定文本基线位置
- 基线x默认在字符串左侧,基线y默认在字符串下方
情况2:指定每个文字的位置
- 情况3:指定路径,并根据路径绘制文字
下面分别细说:
文字的样式(大小,颜色,字体等)具体由画笔Paint控制,详细请会看上面基础的介绍
情况1:指定文本开始的位置
- 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
情况2:分别指定文本的位置
- 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
情况3:指定路径,并根据路径绘制文字
关于Path类的使用请看我写的文章:Path类的最全面详解 - 自定义View应用系列
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
4.2.4 绘制图片
绘制图片分为:绘制矢量图(drawPicture)和 绘制位图(drawBitmap)
a. 绘制矢量图(drawPicture)
- 作用:绘制矢量图的内容,即绘制存储在矢量图里某个时刻Canvas绘制内容的操作
矢量图(Picture)的作用:存储(录制)某个时刻Canvas绘制内容的操作
- 应用场景:绘制之前绘制过的内容
1. 相比于再次调用各种绘图API,使用Picture能节省操作 & 时间
2. 如果不手动调用,录制的内容不会显示在屏幕上,只是存储起来
特别注意:使用绘制矢量图时前请关闭硬件加速,以免引起不必要的问题!
具体使用方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
一般使用的具体步骤
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
步骤1:创建Picture对象
- 1
步骤2:开始录制
- 1
- 2
- 3
步骤3:绘制内容 or 操作Canvas
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
步骤4:结束录制
- 1
步骤5:将存储在Picture的绘制内容绘制出来
有三种方法:
- Picture.draw (Canvas canvas)
- Canvas.drawPicture()
- PictureDrawable.draw()
将Picture包装成为PictureDrawable
主要区别如下:
方法1:Picture提供的draw()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
方法2:Canvas提供的drawPicture()
不会影响Canvas状态
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
方法3:使用PictureDrawable的draw方法绘制
将Picture包装成为PictureDrawable
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
b. 绘制位图(drawBitmap)
- 作用:将已有的图片转换为位图(Bitmap),最后再绘制到Canvas上
位图,即平时我们使用的图片资源
获取Bitmap对象的方式
要绘制Bitmap,就要先获取一个Bitmap对象,具体获取方式如下:
特别注意:绘制位图(Bitmap)是读取已有的图片转换为Bitmap,最后再绘制到Canvas。
所以:
- 对于第1种方式:排除
对于第2种方式:虽然满足需求,但一般不推荐使用
具体请自行了解关于Drawble的内容
对于第3种方式:满足需求,下面会着重讲解
通过BitmapFactory获取Bitmap (从不同位置获取):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
绘制Bitmap
绘制Bitmap共有四种方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
方法1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
方法2
- 1
- 2
- 3
- 4
- 5
方法3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
特别注意的是:如果src规定绘制图片的区域大于dst指定显示的区域的话,那么图片的大小会被缩放。
方法3的应用场景:
便于素材管理
当我需要画很多个图时,如果1张图=1个素材的话,那么管理起来很不方便;如果素材都放在一个图,那么按需绘制会便于管理实现动态效果
动态效果 = 逐渐绘制图形部分,如下:
在绘制时,只需要一个资源文件,然后逐渐描绘就可以
绘制过程如下:
4.2.5 绘制路径
- 1
- 2
关于Path类,具体请看我写的文章: Path类的最全面详解 - 自定义View应用系列
4.2.6 画布操作
- 作用:改变画布的性质
改变之后,任何的后续操作都会受到影响
A. 画布变换
a. 平移(translate)
- 作用:移动画布(实际上是移动坐标系,如下图)
- 具体使用
- 1
- 2
- 3
- 4
b. 缩放(scale)
- 作用:放大 / 缩小 画布的倍数
- 具体使用:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
我将用下面的例子说明缩放的使用和缩放中心的意义。
- 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
当缩放倍数为负数时,会先进行缩放,然后根据不同情况进行图形翻转:
(设缩放倍数为(a,b),旋转中心为(px,py)):
- a<0,b>0:以px为轴翻转
- a>0,b<0:以py为轴翻转
- a<0,b<0:以旋转中心翻转
具体如下图:(缩放倍数为1.5,旋转中心为(0,0)为例)
c. 旋转(rotate)
注意:角度增加方向为顺时针(区别于数学坐标系)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
d. 错切(skew)
- 作用:将画布在x方向倾斜a角度、在y方向倾斜b角度
- 具体使用:
- 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
B. 画布裁剪
即从画布上裁剪一块区域,之后仅能编辑该区域
特别注意:其余的区域只是不能编辑,但是并没有消失,如下图
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
这里特别说明一下参数Region.Op op
作用:在剪下多个区域下来的情况,当这些区域有重叠的时候,这个参数决定重叠部分该如何处理,多次裁剪之后究竟获得了哪个区域,有以下几种参数:
以三个参数为例讲解:
Region.Op.DIFFERENCE:显示第一次裁剪与第二次裁剪不重叠的区域
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
Region.Op.REPLACE:显示第二次裁剪的区域
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
Region.Op.INTERSECT:显示第二次与第一次的重叠区域
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
关于其他参数,较为简单,此处不作过多展示。
C. 画布快照
这里先理清几个概念
- 画布状态:当前画布经过的一系列操作
- 状态栈:存放画布状态和图层的栈(后进先出)
- 画布的构成:由多个图层构成,如下图
- 在画布上操作 = 在图层上操作
- 如无设置,绘制操作和画布操作是默认在默认图层上进行
- 在通常情况下,使用默认图层就可满足需求;若需要绘制复杂的内容(如地图),则需使用更多的图层
- 最终显示的结果 = 所有图层叠在一起的效果
a. 保存当前画布状态(save)
- 作用:保存画布状态(即保存画布的一系列操作)
- 应用场景:画布的操作是不可逆的,而且会影响后续的步骤,假如需要回到之前画布的状态去进行下一次操作,就需要对画布的状态进行保存和回滚
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
b. 保存某个图层状态(saveLayer)
- 作用:新建一个图层,并放入特定的栈中
- 具体使用
使用起来非常复杂,因为图层之间叠加会导致计算量成倍增长,营尽量避免使用。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
c. 回滚上一次保存的状态(restore)
- 作用:恢复上一次保存的画布状态
- 具体使用
- 1
- 2
- 3
d. 回滚指定保存的状态(restoreToCount)
- 作用:恢复指定状态;将指定位置以及以上所有状态出栈
- 具体使用:
- 1
- 2
e. 获取保存的次数(getSaveCount)
- 作用:获取保存过图层的次数
即获取状态栈中保存状态的数量
- 1
- 2
- 3
总结
对于画布状态的保存和回滚的套路,一般如下:- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
5. 总结
- 通过阅读本文,相信你已经全面了解Canvas类的使用;
- 如果希望继续了解自定义View的原理,请参考我写的文章:
自定义View基础 - 最易懂的自定义View原理系列(1)
自定义View Measure过程 - 最易懂的自定义View原理系列(2)
自定义View Layout过程 - 最易懂的自定义View原理系列(3)
自定义View Draw过程- 最易懂的自定义View原理系列(4)
- 接下来,我将继续对自定义View的应用进行分析,有兴趣的可以继续关注Carson_Ho的安卓开发笔记
请帮顶或评论点赞!因为你们的赞同/鼓励是我写作的最大动力!
- Canvas类的最全面详解
- Canvas类的最全面详解
- Path类的最全面详解
- Path类的最全面详解
- 最全面的Android Webview详解
- 最全面的 MySQL 索引详解
- 最全面的Android Webview详解
- (转)最全面的Android Webview详解
- 最全面的Android Webview详解
- 最全面的Android Webview详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- Android:最全面的 Webview 详解
- 华为手机 java.lang.RuntimeException: Unknown camera error(-1)
- 4.5.2.4_浮雕滤镜
- webuploader的参数
- 学习笔记—C语言基础篇07
- 多线程-多进程的通俗解释
- Canvas类的最全面详解
- Failed to execute goal com.mycila:license-maven-plugin:3.0
- java发送邮件(发一封)
- datatable ajax加载自定义的返回参数
- 手把手教你写一个完整的自定义View
- Unity5的关卡切换
- maven 配置文件加载错误的问题
- Linux下修改root密码以及找回root密码的方法
- npm简单使用