Android 中自定义View(二)

系列文章之 Android中自定义View(一)
系列文章之 Android中自定义View(二)
系列文章之 Android中自定义View(三)
系列文章之 Android中自定义View(四)
系列文章之 Android中自定义View(xml绘图)
本文出自:
http://www.jianshu.com/u/a1251e598483

我们在使用各种App时都会看到好多漂亮的效果,说实话有的效果真的很好看,所以觉得能写出这些效果的人都好厉害的说,自定义View 在Android 进阶相关的图书中都是必会内容,我也一直看过大概的自定义View 的内容,看过之后还是觉得不够详细,上手还是抓瞎. 刚好网上 扔物线 大神 写了一个自定义view 的详细教程. http://hencoder.com .如果想学习自定义View的同学请去 大神那里围观,本文是记录自己学习 自定义View 的理解和收获,也是一个记录吧,等到用的时候比较容易找到.

我是分割线,下面开始本文内容--------------------------

自定义View分为以下几个部分

  • Canvas 的 drawXXX() 系列方法及 Paint 最常见的使用
  • Paint 的完全攻略
  • Canvas 对绘制的辅助——范围裁切和几何变换。
  • 使用不同的绘制方法来控制绘制顺序
今天这篇就是第二部分: Paint的使用

Paint 的 API 大致可以分为 4 类:

  • 颜色
  • 效果
  • drawText() 相关
  • 初始化

1 颜色

Canvas
绘制的内容,有三层对颜色的处理:


1.1 基本颜色

像素的基本颜色,根据绘制内容的不同而有不同的控制方式: Canvas的颜色填充类方法
drawColor/RGB/ARGB() 的颜色,是直接写在方法的参数里,通过参数来设置的; drawBitmap() 的颜色,是直接由 Bitmap对象来提供的(上期也讲过了);除此之外,是图形和文字的绘制,它们的颜色就需要使用 paint参数来额外设置了。


Paint 设置颜色的方法有两种:一种是直接用 Paint.setColor/ARGB() 来设置颜色,另一种是使用 Shader 来指定着色方案。

1.1.1 直接设置颜色

1.1.1.1 setColor(int color)

canvas.drawRect(30, 30, 230, 180, paint);

paint.setColor(Color.parseColor("#FF9800"));  
canvas.drawLine(300, 30, 450, 180, paint);
1.1.1.2 setARGB(int a, int r, int g, int b)

其实和 setColor(color) 都是一样一样儿的,只是它的参数用的是更直接的三原色与透明度的值。

1.1.2 setShader(Shader shader) 设置 Shader

除了直接设置颜色, Paint 还可以使用 Shader 。

Shader 这个英文单词很多人没有见过,它的中文叫做「着色器」,也是用于设置绘制颜色的。「着色器」不是 Android 独有的,它是图形领域里一个通用的概念,它和直接设置颜色的区别是,着色器设置的是一个颜色方案,或者说是一套着色规则。当设置了 Shader 之后,Paint 在绘制图形和文字时就不使用 setColor/ARGB() 设置的颜色了,而是使用 Shader 的方案中的颜色。

在 Android 的绘制里使用 Shader ,并不直接用 Shader 这个类,而是用它的几个子类。具体来讲有 LinearGradient RadialGradient SweepGradient BitmapShader ComposeShader 这么几个:

1.1.2.1 LinearGradient 线性渐变

设置两个点和两种颜色,以这两个点作为端点,使用两种颜色的渐变来绘制颜色。就像这样:
Shader shader = new LinearGradient(100,100,500,500,Color.parseColor("#E91E63"), Color.parseColor("#2196F3"),Shader.TileMode.CLAMP);
paint.setShader(shader);
...
canvas.drawCircle(300, 300, 200, paint);


构造方法:
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile) 。

参数:
x0 y0 x1 y1:渐变的两个端点的位置
color0 color1 是端点的颜色
tile:端点范围之外的着色规则,类型是 TileMode。TileMode 一共有 3 个值可选: CLAMP, MIRROR 和 REPEAT。CLAMP (夹子模式???算了这个词我不会翻)会在端点之外延续端点处的颜色;MIRROR 是镜像模式;REPEAT 是重复模式。具体的看一下例子就明白。

CLAMP:



MIRROR:



REPEAT:
1.1.2.2 RadialGradient 辐射渐变

辐射渐变很好理解,就是从中心向周围辐射状的渐变。大概像这样:
Shader shader = new RadialGradient(300, 300, 200, Color.parseColor("#E91E63"), Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);
paint.setShader(shader);
...
canvas.drawCircle(300, 300, 200, paint);


构造方法:
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, TileMode tileMode)。

参数:
centerX centerY:辐射中心的坐标
radius:辐射半径
centerColor:辐射中心的颜色
edgeColor:辐射边缘的颜色
tileMode:辐射范围之外的着色模式。 同样titlMode有三种模式 CLAMP;MIRROR;REPEAT; 和上面的类似

1.1.2.3 SweepGradient 扫描渐变

又是一个渐变。「扫描渐变」这个翻译我也不知道精确不精确。大概是这样:
Shader shader = new RadialGradient(300, 300, 200, Color.parseColor("#E91E63"), Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);
paint.setShader(shader);
...
canvas.drawCircle(300, 300, 200, paint);


构造方法: SweepGradient(float cx, float cy, int color0, int color1)

参数: cx cy:扫描的中心
color0:扫描的起始颜色
color1:扫描的终止颜色

1.1.2.4 BitmapShader
Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);  
paint.setShader(shader);

...

canvas.drawCircle(300, 300, 200, paint);  

构造方法:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

参数:
bitmap:用来做模板的 Bitmap 对象
tileX:横向的 TileMode
tileY:纵向的 TileMode。
1.1.2.5 ComposeShader 混合着色器

所谓混合,就是把两个 Shader 一起使用。

Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.batman);  
Shader shader1 = new BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

// 第二个 Shader:从上到下的线性渐变(由透明到黑色)
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.batman_logo);  
Shader shader2 = new BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

// ComposeShader:结合两个 Shader
Shader shader = new ComposeShader(shader1, shader2, PorterDuff.Mode.SRC_OVER);  
paint.setShader(shader);

...

canvas.drawCircle(300, 300, 300, paint);  

构造方法:ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

参数:
shaderA, shaderB:两个相继使用的 Shader
mode: 两个 Shader 的叠加模式,即 shaderA 和 shaderB 应该怎样共同绘制。它的类型是 PorterDuff.Mode 。
具体 PorterDuff.Mode是什么 情况 可以参考 https://developer.android.com/reference/android/graphics/PorterDuff.Mode.html

1.2 setColorFilter(ColorFilter colorFilter)

ColorFilter 这个类,它的名字已经足够解释它的作用:为绘制设置颜色过滤。颜色过滤的意思,就是为绘制的内容设置一个统一的过滤策略,然后 Canvas.drawXXX() 方法会对每个像素都进行过滤后再绘制出来。

在 Paint 里设置 ColorFilter ,使用的是 Paint.setColorFilter(ColorFilter filter) 方法。 ColorFilter 并不直接使用,而是使用它的子类。它共有三个子类:LightingColorFilter PorterDuffColorFilter 和 ColorMatrixColorFilter。

1.2.1 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  

ColorFilter lightingColorFilter = new LightingColorFilter(0x00ffff, 0x000000);
paint.setColorFilter(lightingColorFilter);

1.2.2 PorterDuffColorFilter

这个 PorterDuffColorFilter 的作用是使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。它的构造方法是 PorterDuffColorFilter(int color, PorterDuff.Mode mode) 其中的 color 参数是指定的颜色, mode 参数是指定的 Mode。同样也是 PorterDuff.Mode ,不过和 ComposeShader 不同的是,PorterDuffColorFilter 作为一个 ColorFilter,只能指定一种颜色作为源,而不是一个 Bitmap。

1.2.3 ColorMatrixColorFilter

这个就厉害了。ColorMatrixColorFilter 使用一个 ColorMatrix 来对颜色进行处理。 ColorMatrix 这个类,内部是一个 4x5 的矩阵:

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

1.3 setXfermode(Xfermode xfermode)

"Xfermode" 其实就是 "Transfer mode",用 "X" 来代替 "Trans" 是一些美国人喜欢用的简写方式。严谨地讲, Xfermode 指的是你要绘制的内容和 Canvas 的目标位置的内容应该怎样结合计算出最终的颜色。但通俗地说,其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。就像这样:

Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

...
canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方  
paint.setXfermode(xfermode); // 设置 Xfermode  
canvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆  
paint.setXfermode(null); // 用完及时清除 Xfermode  

又是 PorterDuff.Mode 。 PorterDuff.Mode
在 Paint一共有三处 API ,它们的工作原理都一样,只是用途不同:


2 效果

效果类的 API ,指的就是抗锯齿、填充/轮廓、线条宽度等等这些。

2.1 setAntiAlias (boolean aa) 设置抗锯齿

抗锯齿默认是关闭的,如果需要抗锯齿,需要显式地打开。另外,除了 setAntiAlias(aa) 方法,打开抗锯齿还有一个更方便的方式:构造方法。创建 Paint 对象的时候,构造方法的参数里加一个 ANTI_ALIAS_FLAG 的 flag,就可以在初始化的时候就开启抗锯齿。

2.2 setStyle(Paint.Style style)

setStyle(style) 也在上一节讲过了,用来设置图形是线条风格还是填充风格的(也可以二者并用):

2.3 线条形状

设置线条形状的一共有 4 个方法:setStrokeWidth(float width), setStrokeCap(Paint.Cap cap), setStrokeJoin(Paint.Join join), setStrokeMiter(float miter) 。

2.3.1 setStrokeWidth(float width)

设置线条宽度。单位为像素,默认值是 0。
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
canvas.drawCircle(150, 125, 100, paint);
paint.setStrokeWidth(5);
canvas.drawCircle(400, 125, 100, paint);
paint.setStrokeWidth(40);
canvas.drawCircle(650, 125, 100, paint);

线条宽度 0 和 1 的区别
默认情况下,线条宽度为 0,但你会发现,这个时候它依然能够画出线,线条的宽度为 1 像素。那么它和线条宽度为 1 有什么区别呢?
其实这个和后面要讲的一个「几何变换」有关:你可以为 Canvas 设置 Matrix 来实现几何变换(如放大、缩小、平移、旋转),在几何变换之后 Canvas 绘制的内容就会发生相应变化,包括线条也会加粗,例如 2 像素宽度的线条在 Canvas 放大 2 倍后会被以 4 像素宽度来绘制。而当线条宽度被设置为 0 时,它的宽度就被固定为 1 像素,就算 Canvas 通过几何变换被放大,它也依然会被以 1 像素宽度来绘制。Google 在文档中把线条宽度为 0 时称作「hairline mode(发际线模式)」。

2.3.2 setStrokeCap(Paint.Cap cap)

设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT。
当线条的宽度是 1 像素时,这三种线头的表现是完全一致的,全是 1 个像素的点;而当线条变粗的时候,它们就会表现出不同的样子:

image

######### 2.3.3 setStrokeJoin(Paint.Join join)

设置拐角的形状。有三个值可以选择:MITER尖角、BEVEL平角和ROUND圆角。默认为MITER

image

######### 2.3.4 setStrokeMiter(float miter)

这个方法是对于setStrokeJoin()的一个补充,它用于设置MITER型拐角的延长线的最大值。所谓「延长线的最大值」,是这么一回事:

当线条拐角为MITER时,拐角处的外缘需要使用延长线来补偿:

image

MITER 型连接点有一个额外的规则:当尖角过长时,自动改用 BEVEL 的方式来渲染连接点。
而这个属性就是setStrokeMiter(miter)方法中的miter参数。miter参数是对于转角长度的限制,具体来讲,是指尖角的外缘端点和内部拐角的距离与线条宽度的比。也就是下面这两个长度的比:

image

用几何知识很容易得出这个比值的计算公式:如果拐角的大小为 θ ,那么这个比值就等于 1 / sin ( θ / 2 ) 。

这个 miter limit 的默认值是 4,对应的是一个大约 29° 的锐角:

image

默认情况下,大于这个角的尖角会被保留,而小于这个夹角的就会被「削成平头」

2.4 色彩优化

Paint 的色彩优化有两个方法: setDither(boolean dither) 和 setFilterBitmap(boolean filter) 。它们的作用都是让画面颜色变得更加「顺眼」,但原理和使用场景是不同的。

2.4.1 setDither(boolean dither)

设置图像的抖动。所谓抖动(注意,它就叫抖动,不是防抖动,也不是去抖动,有些人在翻译的时候自作主张地加了一个「防」字或者「去」字,这是不对的),是指把图像从较高色彩深度(即可用的颜色数)向较低色彩深度的区域绘制时,在图像中有意地插入噪点,通过有规律地扰乱图像来让图像对于肉眼更加真实的做法。
比如向 1 位色彩深度的区域中绘制灰色,由于 1 位深度只包含黑和白两种颜色,在默认情况下,即不加抖动的时候,只能选择向上或向下选择最接近灰色的白色或黑色来绘制,那么显示出来也只能是一片白或者一片黑。而加了抖动后,就可以绘制出让肉眼识别为灰色的效果了:

image

瞧,像上面这样,用黑白相间的方式来绘制,就可以骗过肉眼,让肉眼辨别为灰色了。

嗯?你说你看不出灰色,只看出黑白相间?没关系,那是因为像素颗粒太大,我把像素颗粒缩小,看到完整效果你就会发现变灰了:

image

再把像素颗粒变小,就会出现上图的灰色. 具体怎么用呢
paint.setDither(true);
只要加这么一行代码,之后的绘制就是加抖动的了。
不过对于现在(2017年)而言, setDither(dither) 已经没有当年那么实用了,因为现在的 Android 版本的绘制,默认的色彩深度已经是 32 位的 ARGB_8888 ,效果已经足够清晰了。只有当你向自建的 Bitmap 中绘制,并且选择 16 位色的 ARGB_4444 或者 RGB_565 的时候,开启它才会有比较明显的效果。
######## 2.4.2 setFilterBitmap(boolean filter)

设置是否使用双线性过滤来绘制Bitmap
图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。效果依然盗维基百科的图:

image

牛逼吧?而且它的使用同样也很简单:

paint.setFilterBitmap(true);  

加上这一行,在放大绘制Bitmap的时候就会使用双线性过滤了。
以上就是Paint的两个色彩优化的方法:setDither(dither),设置抖动来优化色彩深度降低时的绘制效果;setFilterBitmap(filterBitmap),设置双线性过滤来优化Bitmap放大绘制的效果。

2.5 setPathEffect(PathEffect effect)

使用PathEffect来给图形的轮廓设置效果。对Canvas所有的图形绘制有效,也就是
drawLine()
drawCircle()
drawPath()
这些方法。大概像这样:

PathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 10);  
paint.setPathEffect(pathEffect);
...
canvas.drawCircle(300, 300, 200, paint);  
image

下面就具体说一下 Android 中的 6 种 PathEffect。PathEffect 分为两类,单一效果的 CornerPathEffect DiscretePathEffect DashPathEffect PathDashPathEffect ,和组合效果的 SumPathEffect ComposePathEffect。

2.5.1 CornerPathEffect

把所有拐角变成圆角。

PathEffect pathEffect = new CornerPathEffect(20);  
paint.setPathEffect(pathEffect);
...
canvas.drawPath(path, paint);  

image

它的构造方法
CornerPathEffect(float radius)的参数radius是圆角的半径。

2.5.2 DiscretePathEffect

把线条进行随机的偏离,让轮廓变得乱七八糟。乱七八糟的方式和程度由参数决定。

PathEffect pathEffect = new DiscretePathEffect(20, 5);
paint.setPathEffect(pathEffect);
...
canvas.drawPath(path, paint);

image

DiscretePathEffect具体的做法是,把绘制改为使用定长的线段来拼接,并且在拼接的时候对路径进行随机偏离。它的构造方法
DiscretePathEffect(float segmentLength, float deviation)的两个参数中,segmentLength是用来拼接的每个线段的长度,deviation是偏离量。这两个值设置得不一样,显示效果也会不一样,具体的你自己多试几次就明白了,这里不再贴更多的图。

2.5.3 DashPathEffect

使用虚线来绘制线条。

PathEffect pathEffect = new DiscretePathEffect(20, 5);
paint.setPathEffect(pathEffect);
...
canvas.drawPath(path, paint);

image

它的构造方法

DashPathEffect(float[] intervals, float phase)中, 第一个参数intervals
是一个数组,它指定了虚线的格式:数组中元素必须为偶数(最少是 2 个),按照「画线长度、空白长度、画线长度、空白长度」……的顺序排列,例如上面代码中的20, 5, 10, 5就表示虚线是按照「画 20 像素、空 5 像素、画 10 像素、空 5 像素」的模式来绘制;第二个参数phase是虚线的偏移量。

2.5.4 PathDashPathEffect

这个方法比 DashPathEffect 多一个前缀 Path ,所以顾名思义,它是使用一个 Path 来绘制「虚线」。具体看图吧:
Path dashPath = ...; // 使用一个三角形来做 dash
PathEffect pathEffect = new PathDashPathEffect(dashPath, 40, 0,
PathDashPathEffectStyle.TRANSLATE);
paint.setPathEffect(pathEffect);
...
canvas.drawPath(path, paint);

image

它的构造方法

PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)中,shape参数是用来绘制的Pathadvance是两个相邻的shape段之间的间隔,不过注意,这个间隔是两个
shape段的起点的间隔,而不是前一个的终点和后一个的起点的距离;phaseDashPathEffect中一样,是虚线的偏移;最后一个参数style,是用来指定拐弯改变的时候shape的转换方式。style的类型为PathDashPathEffect.Style,是一个enum,具体有三个值:

  • TRANSLATE:位移
  • ROTATE:旋转
  • MORPH:变体
image
2.5.5 SumPathEffect

这是一个组合效果类的PathEffect。它的行为特别简单,就是分别按照两种
PathEffect分别对目标进行绘制。

PathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 10);  
paint.setPathEffect(pathEffect);
...
canvas.drawCircle(300, 300, 200, paint);  
image
2.5.6 ComposePathEffect

这也是一个组合效果类的 PathEffect 。不过它是先对目标 Path 使用一个 PathEffect,然后再对这个改变后的 Path 使用另一个 PathEffect。

PathEffect dashEffect = new DashPathEffect(new float[]{20, 10}, 0);  
PathEffect discreteEffect = new DiscretePathEffect(20, 5);  
pathEffect = new ComposePathEffect(dashEffect, discreteEffect);
...
canvas.drawPath(path, paint);  
image

它的构造方法 ComposePathEffect(PathEffect outerpe, PathEffect innerpe) 中的两个 PathEffect 参数, innerpe 是先应用的, outerpe 是后应用的。所以上面的代码就是「先偏离,再变虚线」。而如果把两个参数调换,就成了「先变虚线,再偏离」。至于具体的视觉效果……我就不贴图了,你自己试试看吧!
上面这些就是 Paint 中的 6 种 PathEffect。它们有的是有独立效果的,有的是用来组合不同的 PathEffect 的,功能各不一样。

2.6 setShadowLayer(float radius, float dx, float dy, int shadowColor)

在之后的绘制内容下面加一层阴影。

paint.setShadowLayer(10, 0, 0, Color.RED);
...
canvas.drawText(text, 80, 300, paint);
效果就是上面这样。方法的参数里, radius 是阴影的模糊范围; dx dy 是阴影的偏移量; shadowColor 是阴影的颜色。
如果要清除阴影层,使用 clearShadowLayer() 。

2.7 setMaskFilter(MaskFilter maskfilter)

为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer() 是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。

到现在已经有两个 setXxxFilter(filter) 了。前面有一个 setColorFilter(filter) ,是对每个像素的颜色进行过滤;而这里的 setMaskFilter(filter) 则是基于整个画面来进行过滤。
MaskFilter 有两种: BlurMaskFilter 和 EmbossMaskFilter。

2.7.1 BlurMaskFilter

模糊效果的 MaskFilter。
paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.NORMAL));
...
canvas.drawBitmap(bitmap, 100, 100, paint);
它的构造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style) 中, radius 参数是模糊的范围, style 是模糊的类型。一共有四种:

NORMAL: 内外都模糊绘制
SOLID: 内部正常绘制,外部模糊
INNER: 内部模糊,外部不绘制
OUTER: 内部不绘制,外部模糊(什么鬼?)

2.7.2 EmbossMaskFilter

浮雕效果的 MaskFilter。
paint.setMaskFilter(new EmbossMaskFilter(new float[]{0, 1, 1}, 0.2f, 8, 10));
...
canvas.drawBitmap(bitmap, 100, 100, paint);
它的构造方法 EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) 的参数里, direction 是一个 3 个元素的数组,指定了光源的方向; ambient 是环境光的强度,数值范围是 0 到 1; specular 是炫光的系数; blurRadius 是应用光线的范围。

2.8 获取绘制的 Path

这是效果类的最后一组方法,也是效果类唯一的一组 get 方法。

这组方法做的事是,根据 paint 的设置,计算出绘制 Path 或文字时的实际 Path。

这里你可能会冒出两个问题:

什么叫「实际 Path」? Path 就是 Path,这加上个「实际」是什么意思?
文字的 Path ?文字还有 Path?
这两个问题(咦好像有四个问号)的答案就在后面的内容里。

2.8.1 getFillPath(Path src, Path dst)

首先解答第一个问题:「实际 Path」。所谓实际 Path ,指的就是 drawPath() 的绘制内容的轮廓,要算上线条宽度和设置的 PathEffect。

默认情况下(线条宽度为 0、没有 PathEffect),原 Path 和实际 Path 是一样的;而在线条宽度不为 0 (并且模式为 STROKE 模式或 FLL_AND_STROKE ),或者设置了 PathEffect 的时候,实际 Path 就和原 Path 不一样了:
通过 getFillPath(src, dst) 方法就能获取这个实际 Path。方法的参数里,src 是原 Path ,而 dst 就是实际 Path 的保存位置。 getFillPath(src, dst) 会计算出实际 Path,然后把结果保存在 dst 里。

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

这里就回答第二个问题:「文字的 Path」。文字的绘制,虽然是使用 Canvas.drawText() 方法,但其实在下层,文字信息全是被转化成图形,对图形进行绘制的。 getTextPath() 方法,获取的就是目标文字所对应的 Path 。这个就是所谓「文字的 Path」。

3 drawText() 相关

Paint 有些设置是文字绘制相关的,即和 drawText() 相关的。

比如设置文字大小:
比如设置文字间隔:
比如设置各种文字效果:

4 初始化类

这一类方法很简单,它们是用来初始化 Paint 对象,或者是批量设置 Paint 的多个属性的方法。

4.1 reset()

重置 Paint 的所有属性为默认值。相当于重新 new 一个,不过性能当然高一些啦。

4.2 set(Paint src)

把 src 的所有属性全部复制过来。相当于调用 src 所有的 get 方法,然后调用这个 Paint 的对应的 set 方法来设置它们。

4.3 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()。

下一篇都是文字绘制类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • Android显示框架:自定义View实践之绘制篇 关于作者 郭孝星,程序员,吉他手,主要从事Android平台基...
    郭孝星阅读 9,690评论 2 38
  • 系列文章之 Android中自定义View(一)系列文章之 Android中自定义View(二)系列文章之 And...
    YoungerDev阅读 4,298评论 3 11
  • 人与人之间最小的差别是智商,最大的差别是坚持。这个道理我想大家都懂,可是为什么有些人能持之以恒,而有些人则浅尝则止...
    大涵Then阅读 434评论 1 0
  • 什么时候一个人要想到自己没有什么?当然是有一个前提条件,当他已经拥有一切的时候。当生活把你选座那个幸运儿,让...
    4Q阅读 403评论 0 3