绘制的基本要素
自定义绘制的最基本的步骤是,提前创建好Paint对象,重写onDraw()
,把绘制代码写在onDraw
里面.
一、Canvas.drawXXX() 和 Paint 基础
-
drawXXX()
方法:在整个绘制区域统一涂上指定的颜色。-
Canvas.drawColor(@ColorInt int color) 颜色填充
-
drawRGB(int r, int g, int b) 和 drawARGB(int a, int r, int g, int b) 作用同上
-
drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆
-
drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形
-
drawPoint(float x, float y, Paint paint) 画点
-
drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 画多个点
-
drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆
left
,top
,right
,bottom
是这个椭圆的左、上、右、下四个边界点的坐标。 -
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线
-
drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 画多条线
-
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 画圆角矩形
-
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形
startAngle
是弧形的起始角度(x 轴的正,是 0 度的位置;顺时针为正角度,逆时针为负角度),sweepAngle
是弧形划过的角度;useCenter
表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形 -
drawPath(Path path, Paint paint) 画自定义图形
path
参数是用来描述图形路径的对象,path
的类型是Path
。Path
可以描述直线、二次曲线、三次曲线、圆、椭圆、弧形、矩形、圆角矩形。Path
有两类方法,一类是直接描述路径的,另一类是辅助的设置或计算1. 直接描述路径
这一类的方法可以分钟两组:添加子图形和画线
-
addXxx()
——添加子图形状path.AddXxx())
+canvas.drawPath(path, paint)
这种写法,和直接使用canvas.drawXxx()
的效果是一样的,所以如果只画一个图形,没必要用Path
,直接用drawXXX
就可以了,drawPath
一般是在绘制组合图形时才会使用。 -
xxxTo
——画线(包括直线和曲线)这一组和上面的区别在于,上一组是添加的完整封闭图像,而这一组添加的只有一条线
-
lineTo(float x, float y) / rLineTo(float x, float y) 从当前位置向目标位置画一条直线
-
quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线,x1, y1 和 x2, y2 则分别是控制点和终点的坐标
贝塞尔曲线:贝塞尔曲线是几何上的一种曲线,它通过起点、控制点和终点描述一条曲线,主要用于计算机图形学
-
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线
-
moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置(不画线了)
moveTo(x,y)
不添加图形,它会设置图形的起点 -
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)画弧形
forceMoveTo
参数用于表述从当前点到弧的起点之间是否要画线,为true是不画线 -
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)
这也是一个添加弧形的方法,它是直接使用
forceMoveTo=true
的简化版本 -
close 封闭当前字图形
把当前的子图形封闭,即由当前位置向当前子图形的起点绘制一条直线
-
2.辅助的设置或计算
-
Path.setFillType(fillType)
前面在说 dir 参数的时候提到, Path.setFillType(fillType) 是用来设置图形自相交时的填充算法的:
FillType的取值有四个:- EVEN_ODD
- WINDING(默认值)
- INVERSE_EVEN_ODD
- INVENRSE_SINDING
后面的两个带有 INVERSE_ 前缀的,只是前两个的反色版本,只要弄懂前两个即可
EVEN_ODD 和 WINDING 的原理
-
EVEN_ODD
即even_odd rule(奇偶原则):对于平面中的任意一点,想任何方向射出一条射线,这条射线和图形相交(不包含相切)的次数如果是奇数,则这个点被认为在图像内部,是要被涂色的区域;如果是偶数,则这个点被认为在图形外部,是不被涂色的区域。
如下为左右两圆相交的示例
-
WINDING
即 non-zero winding rule(非零环绕数原则):首先他需要你图形的所有线条都是有绘制方向的,然后同样是从平面中的点向任意方向射出一条射线,以0为初始值,对于射线和图形的所有焦点,遇到每个顺时针的交点把结果加1,遇到每个逆时针的交点把结果减1,最终把所有的交点都算上,如果得到的结果不是0,则认为这个点在图形内部,是要被涂色的区域;如果是0则认为这个点在图形的外部,是不被涂色的区域
-
-
drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 画 Bitmap
-
drawText(String text, float x, float y, Paint paint) 绘制文字
-
-
Paint基础
-
Paint.setColor(int color) 设置颜色
设置画笔的颜色,后面使用该画笔绘制的图形将用这里设置的颜色填充
-
Paint.setStyle(Paint.Style style) 设置绘制模式
Style有三种模式:FILL
,STROKE
,FILL_AND_STROKE
。FILL
是填充模式,STROKE
是画线模式(描边),FILL_AND_STROKE
是两种模式一并使用,即画线有填充。它的默认只是FILL
,填充模式 -
Paint.setStrokeWidth(float width) 设置线条宽度
设置描边的线条宽度
-
Paint.setAntiAlias(boolean aa) 设置抗锯齿开关
或者在
new Paint()
的时候加上一个ANTI_ALIAS_FLAG
参数也可以没有开启抗锯齿的时候,图形或有毛边现象。对比效果如下
为什么抗锯齿开启之后的图形边缘会更加平滑呢?因为抗锯齿的原理是:修改图形边缘处的像素颜色,从而让图形在肉眼看来具有更加平滑的感觉
未开启抗锯齿的圆,所有像素都是同样的黑色,而开启了抗锯齿的圆,边缘的颜色被略微改变了。这种改变可以让人眼有边缘平滑的感觉,但从某种角度讲,它也造成了图形的颜色失真。 -
Paint.setTextSize(float textSize) 设置文字的大小
Android 的坐标系:
在 Android 里,每个 View 都有一个自己的坐标系,彼此之间是不影响的。这个坐标系的原点是 View 左上角的那个点;水平方向是 x 轴,右正左负;竖直方向是 y 轴,下正上负,
如下图
-
二、Paint详解
henCoder链接https://juejin.im/post/596baf5f6fb9a06bb15a3df9
Paint的API大致可以分为4类
- 颜色
- 效果
- drawText相关
- 初始化
1)颜色
Canvas
绘制的内容,有三层对颜色的处理:基本颜色,ColorFilter,Xfermode
-
基本颜色
像素的基本颜色,根据绘制内容而有不同的控制方式:Canvas的颜色填充类方法
drawColor/RGB/ARGB()
的颜色,直接写在方法参数里;drawBitmap()
的颜色,是有Bitmap
对象来提供的;除此之外的图形和文字的绘制,他们的颜色使用paint参数来额外设置;paint设置颜色有两种方法:一种是直接用
Paint.setColor/ARGB()
来设置颜色,另一种是使用Shader
来指定着色方案paint使用 setShader(Shader shader)方法 来设置Shader
Shader,中文名为着色器,它是图像领域里的一个通用概念,它和直接设置颜色的区别是,着色器设置的是一个颜色方案,或者说是一套着色规则。当设置了Shader之后,paint在绘制图形和文字时就不使用
setColor/ARGB()
设置的颜色了,而是使用Shader
的方案中的颜色Android中绘制不直接使用
Shader
,而是使用它的几个子类。包括-
LinearGradient
线性渐变设置两个点和两种颜色,以这两个点位断点,使用两种颜色的渐变来绘制颜色
-
RadiaGraient
辐射渐变从中心向周围辐射状的渐变。需要设置中心的颜色和边缘颜色
-
SweepGradient
扫描渐变以扫描中心为原点,从x轴正向开始,从开始颜色扫描360度到终止颜色
-
BitmapShader
用Bitmap
来着色用Bitmap的像素来作为图形和文字的填充
-
ComposeShader
把两个Shader
混合使用
构造方法:
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
最后的参数mode用于指定两个Shader的叠加模式
PorterDuff.Mode
PorterDuff.Mode
是用来指定两个图像共同绘制时的颜色策略的。【颜色策略】是指把***源图像绘制到目标图像*** 处时应该怎样确定二者结合后的颜色。PorterDuff.Mode
一共有 17 个,可以分为两类 -
-
setColorFilter(ColorFilter colorFilter)
ColorFilter,为绘制设置颜色过滤,也就是为绘制的内容设置一个统一的过滤策略,然后Canvas.drawXxx()方法会对每个像素都进行过滤后再绘制出来。
在Android里ColorFilter并不被直接使用,而是使用他的三个子类
- LightingColorFilter
LightingColorFilter用来模拟简单的光照效果。
LightingColorFilter的构造方法是
LightingColorFilter(int mul, int add)
,参数里的mul
和add
都是和颜色值格式相同的int值,mul
用来和目标像素相乘,add
用来和目标相素相加R' = R * mul.R / 0xff + add.R G' = G * mul.G / 0xff + add.G B' = B * mul.B / 0xff + add.B
-
PorterDuffColorFilter
PorterDuffColorFilter
的作用是使用一个指定的颜色和一种指定的PorterDuff.Mode
来与绘制对象进行合成,它的构造方法是PorterDuffColorFilter(int color, PorterDuff.Mode mode)
其中的color
参数是指定的颜色,mode
参数是指定的PorterDuff.Mode
,跟ComposeShader
的一样,不同的是PorterDuffColorFilter
只能指定一种颜色作为源,而不是Bitmap
-
ColorMatrixColorFilter
ColorMatrixColorFilter
使用一个ColorMatrix
来对颜色进行处理。ColorMatrix
这个类,内部是一个4*5的矩阵[ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]
通过计算,
ColorMatrix
可以把要绘制的像素进行转换,对于颜色[R,G,B,A],转换算法为R’ = a*R + b*G + c*B + d*A + e; G’ = f*R + g*G + h*B + i*A + j; B’ = k*R + l*G + m*B + n*A + o; A’ = p*R + q*G + r*B + s*A + t;
-
setXfermode(Xfermode xfermode)
“Xfermode"其实就是"Transfer mode",用"X"来代替”Trans“是美国人喜欢用的简写方式.
Xfermode
指的是你要绘制的内容和Canvas
的目标位置的内容应该怎样结合计算出最终的颜色。就是以 要绘制的内容 作为 源图像,以View中 已有的内容 作为 目标图像,选取一个 PorterDuff.Mode
作为绘制内容的颜色处理方案。可以这么记忆:把源图像(要绘制的内容)绘制到目标图像(已有的内容)
实际上创建 Xfermode
的的时候要使用它的子类PorterDuffXfermode
。
**Xfermode **注意事项
使用离屏缓冲
由于在绘制是,目标图像是真个
View
的所有内容,而且View
自身的底色并不是默认的透明色,而且是一种不知道的颜色,导致在与源图像混合计算后,不仅仅是已有的图像进行了覆盖,还使得图像之外都变成里黑色,就像下面
导致要想使用
serXfermode()
正常绘制,必须使用离屏缓存把内容绘制在额外的层上,再把绘制的内容贴会到View中。
使用离屏缓冲有如下两种方式
Canvas.saveLaver()
saveLayer()
可以做短时的离屏缓冲。只要绘制前调用该方法保存,绘制后恢复即可int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方 paint.setXfermode(xfermode); // 设置 Xfermode canvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆 paint.setXfermode(null); // 用完及时清除 Xfermode canvas.restoreToCount(saved);
View.setLayerType
View.setLayerType()
是直接把整个View
都绘制在离屏缓冲中。
setLayerType(LAYER_TYPE_HARDWARE)
是使用GPU来缓冲,
setLayerType(LAYER_TYPE_SOFTWARE)
是直接用一个Bitmap
来缓冲控制好透明区域
使用Xfermode来绘制内容,除了上面还应该注意它的透明区域不要太小,要让它足够覆盖到要和它结合的绘制的内容,否则结果无法达到我们的目标
2)效果
-
setAntiAlias (boolean aa) 设置抗锯齿
-
setStyle(Paint.Style style)
-
线条形状
设置线条形状一共有四个方法
-
setStrokeWidth(float width)
设置线条宽度,默认值为0
线条宽度为0和1的区别
在进行几何变换后,当线条放大到2倍时,如果线条宽度为1,则线条宽度变为2个像素,而若线条宽度为0则它被锁定为1个像素,不受几何变换的影响
Google 在文档中把线条宽度为 0 时称作「hairline mode(发际线模式)」。
-
setStrokeCap(Paint.Cap cap)
设置线头的形状。线头形状有三种
BUTT
平头、ROUND
圆头、SQUARE
方头。默认为BUTT
。当线条的宽度是1像素时,这三种线头的表现是一致的,全为一个像素的点,但当线条变粗时,他们将表现出不同的样子
-
setStrokeJoin(Paint.Join join)
设置拐角的形状。有三个值可以选择:
MITER
尖角、BEVEL
平角和ROUND
圆角。默认为MITER
。 -
setStrokeMiter(float miter)
这个方法是对
setStrokeJoin(Paint.Join join)
的补充,它用于设置MITER
型拐角的延长线的最大值。当线条拐角为
MITER
时,拐角处的边缘需要使用延长线来补偿但是如果拐角的角度太小,就有可能会出现连接点过长的情况
所以为了避免过长的尖角出现,
MITER
型连接点在尖角太长时,自动改用BEVEL
的方式来渲染连接点。setStrokeMiter(miter)
方法中的miter
参数就是对于转角长度的限制,具体是指尖角的外援端点和内部拐角的距离与线条宽度的比。如下
-
-
色彩优化
-
setDither(bolean dither)
设置图像抖动抖动是指把图像从较高色彩深度(可用的颜色数)向较低色彩深度的区域绘制时(譬如 32 位的
ARGB_8888
->16 位色的ARGB_4444
),在图像中有意地插入噪点,通过有规律的扰乱图像来让图像对于肉眼更加真实的做法。抖动不可用在纯色的绘制中。在实际的场景中,抖动更多的作用是在图像降低色彩深度绘制时,避免出现大片的色带与色块。 -
setFilterBitmap(boolean filter)
设置是否使用双线性过滤来绘制Bitmap
图像在方法绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克效应;而如果开启了双线性过滤,就可以让结果图像显得更加平滑
-
-
setPathEffect(PathEffect effect)
设置图像轮廓效果Andorid 有6种
PathEffect
-
CornerPathEffect
把所有拐角都变为圆角 -
DiscretePathEffect
把线条进行随机的偏离具体的显示方式是:把绘制改为使用定长的线段来拼接,并且在拼接的时候对路径进项随机偏离
构造方法
DiscretePathEffect(float segmentLength, float deviation)
,其中segmentLength
是用来拼接的每个线段的长度,deviation
是偏离量 -
DashPathEffect
使用虚线来绘制线条构造方法
DashPathEffect(float[] intervals, float phase)
,第一个参数intervals
是一个数组,指定了虚线的格式:数组中的元素必须为大于0的偶数,按照[划线长度、空白长度、划线长度、空白长度…]的顺序排列,第二个参数phase
是虚线的便宜量 -
PathDashPathEffect
使用路径来绘制虚线线条构造方法
PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)
,其中第一参数shape
是用来绘制的Path
;advance
是两个相邻的shape
段起点之间的间隔;phase
和上面一样是虚线的偏移;最后一个参数style
是来用来指定拐弯改变时shape
的转换方式。style
的类型为PathDashPathEffect.Style
,是一个枚举,有三个值:TRANSLATE
:位移ROTATE
:旋转MORPH
:变体
-
SumPathEffect
组合两种PathEffect
分别对目标同时进行绘制 -
ComposePathEffect
组合两个PathEffect
,先使用一种PathEffect
,然后在对结果使用另一种PathEffect
进行改变
构造方法
ComposePathEffect(PathEffect outerpe, PathEffect innerpe)
,其中innerpe
是先应用的,outerpe
是后应用的。注意:
PathEffect
在有些情况下不支持硬件加速,需要关闭硬件加速才能正常使用:Canvas.drawLine()
和Canvas.drawLines()
方法画直线时,setPathEffect()
是不支持硬件加速的;PathDashPathEffect
对硬件加速的支持也有问题,所以当使用PathDashPathEffect
的时候,最好也把硬件加速关了。 -
-
setShadowLayer(float radius, float dx, float dy, int shadowColor)
在之后绘制的内容下面加一层阴影,方法的参数里,
radius
是阴影的模糊范围;dx
dy
是阴影的偏移量;shadowColor
是阴影的颜色如果要清楚阴影层,使用
cleanShadowLayer
注意:
- 在硬件加速时,
setShadowLayer()
只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。 - 如果
shadowColor
是半透明的,阴影的透明度就是shadowColor
自己的透明度;而如果shadowColor
是不透明的,阴影的透明度就是paint
的透明度
- 在硬件加速时,
-
setMaskFilter(MaskFilter maskfilter)
为之后的绘制的设置
MashFilter
。它与上面的阴影相反,他是绘制在绘制层上方的附加效果(遮罩),类似于背景和前景MaskFilter
有两种-
BlurMaskFilter
模糊效果的MaskFilter
构造方法
BlurMaskFilter(float radius, BlurMaskFilter.Blur style)
,其中,radius
参数是模糊的范围,style
四模糊的类型。一种有四种:-
NORMAL
:内外都模糊绘制 -
SOLID
:内部正常绘制,外部模糊 -
INNER
:内部模糊,外部不绘制 -
OUTER
:内部不绘制,外部模糊
-
-
EmbossMashFilter
浮雕效果的
MaskFilter
构造方法
EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)
,其中direction
是一个 3 个元素的数组,指定了光源的方向;ambient
是环境光的强度,数值范围是 0 到 1;specular
是炫光的系数;blurRadius
是应用光线的范围
-
-
获取绘制的Path
根据
paint
的设置,计算出绘制Path
或文字时的 实际 Path。所谓「实际Path
,指的就是drawPath()
的绘制内容的轮廓,要算上线条宽度和设置的PathEffect
。-
getFillPath(Path src, Path dst)
src
是原Path
,而dst
就是实际Path
的保存位置。getFillPath(src, dst)
会计算出实际Path
,然后把结果保存在dst
里。 -
getTextPath(String text, int start, int end, float x, float y, Path path)
/getTextPath(char[] text, int index, int count, float x, float y, Path path)
getTextPath()
方法,获取的就是目标文字所对应的Path
。这个就是所谓「文字的Path
」
3)初始化类
这一类方法专门用来初始化
Paint
对象,或者批量设置Paint
的多个属性的方法-
reset()
重置
Paint
的所有属性为默认值,相当于一个重新new
一个,不过耗性能低 -
set(Paint src)
把
src
的所有属性全部复制过来。 -
setFlags(int flags)
批量设置flags。相当于依次调用它们的
set
方法paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
相当于
paint.setAntiAlias(true); paint.setDither(true);
setFlags(flags)
对应的get
方法是int getFlags()
。
-
三、文字的绘制
1)Canvas绘制文字的方式
-
drawText(String text, float x, float y, Paint paint)
text
是文字内容,x
和y
是文字的坐标。这个坐标并不是文字的左上角,而是一个与左下角比较接近的位置。drawText()
参数中的y
,指的是文字的**基线( baseline )** 的位置。x
点并不是文字左边的位置,而是比它的左边再往左一点点。是因为绝大多数的字符,它们的宽度都是要略微大于实际显示的宽度的。字符的左右两边会留出一部分空隙,用于文字之间的间隔,以及文字和边框的间隔。而往左的那一点点就是文字的间隔 -
drawTextRun()
这个方法对中国人没用,忽略
-
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
沿着一条
Path
来绘制文字。hOffset
和vOffset
。它们是文字相对于Path
的水平偏移量和竖直偏移量,利用它们可以调整文字的位置注意:
drawTextOnPath()
使用的Path
,拐弯处全用圆角,别用尖角。否则会像上面一样很难看 -
StaticLayout
Canvas.drawText()
只能绘制单行的文字,而不能换行。而StaticLayout
支持换行,StaticLayout
并不是一个View
或者ViewGroup
,而是android.text.Layout
的子类,它是纯粹用来绘制文字的。它既可以为文字设置宽度上限来让文字自动换行,也会在\n
处主动换行。构造方法是
StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
,其中参数里width
是文字区域的宽度,文字到达这个宽度后就会自动换行;
align
是文字的对齐方向;
spacingmult
是行间距的倍数,通常情况下填 1 就好;
spacingadd
是行间距的额外增加值,通常情况下填 0 就好;
includeadd
是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。使用方法:
String text1 = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."; StaticLayout staticLayout1 = new StaticLayout(text1, paint, 600, Layout.Alignment.ALIGN_NORMAL, 1, 0, true); canvas.save(); staticLayout1.draw(canvas); canvas.restore();
2)Paint对文字绘制的辅助
-
设置显示效果类
-
setTextSize(float textSize)
设置文字大小。 -
setTypeface(Typeface typeface)
设置字体。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);
typeface 指的是某套字体(即 font family ),而 font 指的是一个 typeface 具体的某个 weight 和 size 的分支。
-
setFakeBoldText(boolean fakeBoldText)
否使用伪粗体。它并不是通过选用更高 weight 的字体让文字变粗,而是通过程序在运行时把文字给「描粗」
-
setStrikeThruText(boolean strikeThruText)
是否加删除线。 -
setUnderlineText(boolean underlineText)
是否条件下划线 -
setTextSkewX(float skewX)
设置文字横向错切角度。 -
setTextScaleX(float scaleX)
设置文字横向放缩 -
setLetterSpacing(float letterSpacing)
设置字符间距 -
setFontFeatureSettings(String settings)
用 CSS 的font-feature-settings
的方式来设置文字。 -
setTextAlign(Paint.Align align)
设置文字的对齐方式一共有三个值:
LEFT
CETNER
和RIGHT
。默认值为LEFT
。 -
setTextLocale(Locale locale)
设置绘制所使用的Locale
-
setHinting(int mode)
设置是否启用字体的 hinting (字体微调)Android 设备大多数都是是用的矢量字体。在字号较大的时候,矢量字体也能够保持字体的圆润。但是当文字的尺寸过小(比如高度小于 16 像素),有些文字会由于失去过多细节而变得不太好看。 hinting 技术就是为了解决这种问题的:通过向字体中加入 hinting 信息,让矢量字体在尺寸过小的时候得到针对性的修正,从而提高显示效果。
-
setSubpixelText(boolean subpixelText)
是否开启次像素级的抗锯齿
-
-
测量文字尺寸类
-
float getFontSpacing()
获取推荐的行距。
获取推荐的两行文字的baseline 的距离。这个值是系统根据文字的字体和字号自动计算的。它的作用是当你要手动绘制多行文字的时候,可以在换行draw的时候确认下一行文字的
y
坐标 -
FontMetircs getFontMetrics()
获取
Paint
的FontMetrics
FontMetrics
提供了几个文字排版方面的数值:ascent
,descent
,top
,bottom
,leading
.
ascent
和descent
是上图中绿色和橙色的线,它们限制普通字符的的顶部和底部范围。在Android中,ascent
的值是图中绿色线和baseline
的相对位移,它的值为负(y坐标向下为正);descent
的值是图中橙色和baseline
的相对位移,它的值为正top
/bottom
的作用是限制所有字形的顶部和底部范围。除了普通字符,有些字形的显示范围会超过ascent
和descent
,但是包括这些特殊字符都不会超过top
和bottom
。同上类似,top
的值是蓝线相对baseline
的位移,值为负;bottom
是红线相对baseline
的值,值为正leading
指的是行的额外间距,即在相邻的两行中上一行的bottom
和下一行的top
的距离leading
的本意是行距,即连个相邻行baseline
中间的距离,但是在很多非专业领域,leading
被用来指代航的额外间距,Android中就是这样。FontMetrics
提供的就是Paint
根据当前字体和字号,得出的这些值的推荐值。它把这些值以变量的形式存储;ascent
和descent
这两个值还可以通过Paint.ascent()
和Paint.descent()
来快捷获取 -
getTextBounds(String text, int start, int end, Rect bounds)
获取文字的显示范围
text
是要测量的文字,start
和end
分别是文字的起始和结束位置,bounds
是存储文字显示范围的对象,方法在测算完成之后会把结果写进 `bounds该方法测量的是文字的显示范围
-
float measureText(String text)
测量文字的宽度并返回
该方法测量的是文字绘制时所占用的宽度,一个文字在界面中,往往需要占用比他的实际显示宽度更多一点的宽度。
-
getTextWidths(String text, float[] widths)
获取字符串中每个字符的宽度,并把结果填入
widths
-
int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
在给出最大宽度的前提下测量文字宽度,如果文字的宽度超出限制,则在临近超限的位置截断文字。
其返回值是截取的文字个数(没有超限时,是文字总个数)。
方法中,
text
是要测量的文字;measureForwards
表示文字的测量方向,true
表示由左往右测量;maxWidth
是给出的宽度上限;measuredWidth
是用于接受数据,而不是用于提供数据的:方法测量完成后会把截取的文字宽度(如果宽度没有超限,则为文字总宽度)赋值给measuredWidth[0]
。这个方法可以用于多行文字的折行计算
-
光标相关
-
getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)
对于文字
text
计算出offset
个字符处光标的x
坐标。start
end
是文字的起始和结束坐标;contextStart
contextEnd
是上下文的起始和结束坐标;isRtl
是文字的方向。 -
getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)
给出一个位置的像素值
advance
,计算出文字text
中最接近这个位置的字符偏移量。start
end
是文字的起始和结束坐标;contextStart
contextEnd
是上下文的起始和结束坐标;isRtl
是文字方向;返回结果是对应的字符偏移量。 -
hasGlyph(String string)
检查指定的字符串中是否是一个单独的字形。
-
-
四、Canvas 对绘制的辅助
-
范围裁切
-
clipRect()
将画布裁切成一个矩形范围,后面的绘制都在这个范围内,超出范围的部分将会被裁掉
-
clipPath
同上,只不过形状不是矩形了,而是一个Path
-
-
几何变换
-
使用
Canvas
来做常见的二维变幻-
Canvas.translate(float dx, float dy)
平移将画布分别沿着x轴平移
dx
y轴平移dy
-
Canvas.rotate(float degrees, float px, float py)
旋转degrees
是旋转角度,同样是对画布的旋转,选装中心是px
,py
-
Canvas.scale(float sx, float sy, float px, float py)
放缩sx
sy
是横向和纵向的放缩倍数;px
py
是放缩的轴心。 -
skew(float sx, float sy)
错切参数里的
sx
和sy
是 x 方向和 y 方向的错切系数
-
-
使用
Matrix
来做变换-
使用
Matrix
来做常见变换一般步骤为:
- 创建
Matrix
对象; - 调用
Matrix
的pre/postTranslate/Rotate/Scale/Skew()
方法来设置几何变换; - 使用
Canvas.setMatrix(matrix)
或Canvas.concat(matrix)
来把几何变换应用到Canvas
。
把
Matrix
应用到Canvas
有两个方法:Canvas.setMatrix(matrix)
:用Matrix
直接替换Canvas
当前的变换矩阵(hencoder 说不同的手机系统中 setMatrix(matrix) 的行为可能不一致,建议用下面的)。Canvas.concat(matrix)
:用Canavas
当前的变换矩阵和Matrix
相乘,及将Canvas
当前的变换叠加上Matrix
的变换
- 创建
-
使用
Matrix
来做自定义变换Matrix
的自定义变换使用的是setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)
方法setPolyToPoly()
的作用是通过多点映射的方式来直接设置变换。多点影射的意思就是把指定的点移动到给出的位置,从而发生形变
src
和dst
是源点集合目标点集;srcIndex
和dstIndex
是第一个点的偏移;pointCount
是采集的点的个数(个数不能大于 4,因为大于 4 个点就无法计算变换了)Matrix matrix = new Matrix(); float pointsSrc = {left, top, right, top, left, bottom, right, bottom}; float pointsDst = {left - 10, top + 50, right + 120, top - 90, left + 20, bottom + 30, right + 20, bottom + 60}; ... matrix.reset(); matrix.setPolyToPoly(pointsSrc, 0, pointsDst, 0, 4); canvas.save(); canvas.concat(matrix); canvas.drawBitmap(bitmap, x, y, paint); canvas.restore();
-
-
-
使用
Camera
来做三维变换-
Camera.rotate*()
三维旋转rotateX(deg)
rotateY(deg)
rotateZ(deg)
rotate(x, y, z)
-
Camera.translate(float x, float y, float z)
移动 -
Camera.setLocation(x, y, z)
设置虚拟相机的位置它的参数的单位不是像素,而是 inch,英寸
Camera 的位置单位是英寸,英寸和像素的换算单位被写死为了 72 像素
-
五、绘制顺序
-
写在
super.onDraw()
的下面由于绘制代码会在原有内容绘制结束之后才执行,所以绘制内容就会盖住控件原来的内容。
常见的情况:为控件增加点缀性内容;比如,在 Debug 模式下绘制出
ImageView
的图像尺寸信息 -
写在
super.onDraw()
的上面由于绘制代码会执行在原有内容的绘制之前,所以绘制的内容会被控件的原内容盖住
一般可以通过在文字的下层绘制纯色矩形来作为「强调色」
-
dispatchDraw()
:绘制子 View 的方法当我们继承一个
ViewGroup
类型的View时,在onDraw
里绘制了内容会正常显示(前提是做了setWillNotDraw(false)
等会让ViewGroup
走Ondraw
方法的设置)但是当我们添加自view后,我们的绘制信息会消失,即使我们的绘制内容写在
super.onDraw()
的下面。这是因为在绘制过程中,每一个
ViewGroup
会先调用自己的onDraw()
来绘制完自己的主体之后再去绘制它的子 View。所以在onDraw
这个方法里我们永远无法让我们的绘制内容显示在子View上方上面说的绘制子View的方法叫做
dispatchDraw()
,我们只要重写这个方法,并把内容写在super.dispatchDraw()
的下面即可 -
绘制过程简述
一个完整的绘制过程会依次绘制以下几个内容:
- 背景
- 主体(
onDraw()
) - 子 View(
dispatchDraw()
) - 滑动边缘渐变和滑动条
- 前景
-
onDrawForeground()
这个方法是 API 23 才引入的,所以在重写这个方法的时候要确认你的
minSdk
达到了 23,不然低版本的手机装上你的软件会没有效果如果你把绘制代码写在了
super.onDrawForeground()
的下面,绘制代码会在滑动边缘渐变、滑动条和前景之后被执行,那么绘制内容将会盖住滑动边缘渐变、滑动条和前景。如果你把绘制代码写在了
super.onDrawForeground()
的上面,绘制内容就会在dispatchDraw()
和super.onDrawForeground()
之间执行,那么绘制内容会盖住子 View,但被滑动边缘渐变、滑动条以及前景盖住:无法在滑动边缘渐变、滑动条和前景之间插入绘制代码
-
draw() 总调度方法
draw() 是绘制过程的总调度方法。一个 View 的整个绘制过程都发生在
draw()
方法里。背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在draw()
方法里的。// View.java 的 draw() 方法的简化版大致结构(是大致结构,不是源码哦): public void draw(Canvas canvas) { ... drawBackground(Canvas); // 绘制背景(不能重写) onDraw(Canvas); // 绘制主体 dispatchDraw(Canvas); // 绘制子 View onDrawForeground(Canvas); // 绘制滑动相关和前景 ... }
如果把绘制代码写在
super.draw()
的下面,那么这段代码会在其他所有绘制完成之后再执行,也就是说,它的绘制内容会盖住其他的所有绘制内容。如果把绘制代码写在
super.draw()
的上面,那么这段代码会在其他所有绘制之前被执行,所以这部分绘制内容会被其他所有的内容盖住,包括背景。是的,背景也会盖住它。
- 在
ViewGroup
的子类中重写除dispatchDraw()
以外的绘制方法时,可能需要调用setWillNotDraw(false)
;- 在重写的方法有多个选择时,优先选择
onDraw()
。因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过onDraw()
的重复执行