Canvas 的文字绘制方法有三个:drawText() drawTextRun() 和 drawTextOnPath() 。
drawText() 是 Canvas 最基本的绘制文字的方法:给出文字的内容和位置, Canvas 按要求去绘制文字。
String text = "Hello HenCoder";
...
canvas.drawText(text, 200, 100, paint);
方法的参数: text 是文字内容,x 和 y 是文字的坐标。但需要注意:这个 坐标并不是文字的左上角,而是一个与左下角比较接近的位置。
而如果像绘制其他内容一样,在绘制文字的时候把坐标填成 (0, 0),文字并不会显 示在 View 的左上角,而是会几乎完全显示在 View 的上方,到了 View 外部看不到 的位置:
drawText() 参数中的 y ,指的是文字的基线( baseline ) 的位置。
众所周知,不同的语言和文字,每个字符的高度和上下位置都是不一样的。要让不 同的文字并排显示的时候整体看起来稳当,需要让它们上下对齐。但这个对齐的方 式,不能是简单的「底部对齐」或「顶部对齐」或「中间对齐」,而应该是一种类 似于「重心对齐」的方式。就像电线上的小鸟一样
而这个用来让所有文字互相对齐的基准线,就是基线( baseline )。 drawText() 方 法参数中的 y 值,就是指定的基线的位置。 说完 y 值,再说说 x 值。从前面图中的标记可以看出来,「Hello HenCoder」绘 制出来之后的 x 点并不是字母 "H" 左边的位置,而是比它的左边再往左一点点。那 么这个「往左的一点点」是什么呢? 它是字母 "H" 的左边的空隙。绝大多数的字符,它们的宽度都是要略微大于实际显 示的宽度的。字符的左右两边会留出一部分空隙,用于文字之间的间隔,以及文字 和边框的间隔。
这个方法对中文和英文都没用,可以不看
它和 drawText() 一样都是绘制文 字,但加入了两项额外的设置——上下文和文字方向——用于辅助一些文字结构比 较特殊的语言的绘制。
- 额外设置一:上下文。 有些语言的文字,字符的形状会互相之间影响:一个字你单独写是一个样,和别 的字放在一起写又是另外一个样。不过由于我们最熟悉的语言——汉语和英语 ——都没有这种情况,
- 额外设置二:文字方向。 除了上下文, drawTextRun() 还可以设置文字的方向,即文字是从左到右还是 从右到左排列的。
沿着一条 Path 来绘制文字。
canvas.drawPath(path, paint); // 把 Path 也绘制出来,理解起来更方便
canvas.drawTextOnPath("Hello HeCoder", path, 0, 0, paint);
记住一条原则: drawTextOnPath() 使用的 Path ,拐弯处全用圆角,别 用尖角。否则会很难看
额外讲一个 StaticLayout 。这个也是使用 Canvas 来进行文字的绘制,不过并不 是使用 Canvas 的方法。
如果需要绘制多行的文字,你必须自行把文字切断后分多次使用 drawText() 来绘制,或者——使用 StaticLayout
String text1 = "Lorem Ipsum is simply dummy text of the printing and
StaticLayout staticLayout1 = new StaticLayout(text1, paint, 600,
Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
String text2 = "a\nbc\ndefghi\njklm\nnopqrst\nuvwx\nyz";
StaticLayout staticLayout2 = new StaticLayout(text2, paint, 600,
Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
...
canvas.save();
canvas.translate(50, 100);
staticLayout1.draw(canvas);
canvas.translate(0, 200);
staticLayout2.draw(canvas);
canvas.restore();
StaticLayout 的构造方法是 StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
,
其中参数里:
-
width 是文字区域的宽度,文字到达这个宽度后就会自动换行;
-
align 是文字的对齐方向;
-
spacingmult 是行间距的倍数,通常情况下填 1 就好;
-
spacingadd 是行间距的额外增加值,通常情况下填 0 就好;
-
includeadd 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制 出现越界。
绘制⽂字:drawText()
⽅式⼀:Paint.getTextBounds() 之后,使⽤ (bounds.top + bounds.bottom) / 2
⽅式⼆:Paint.getFontMetrics() 之后,使⽤ (fontMetrics.ascend + fontMetrics.descend) / 2
⽤ getTextBounds() 之后的 left 来计算
⽤ breakText() 来计算
Paint 对文字绘制的辅助,有两类方法:设置显示效果的和测量文字尺寸的。
设置文字大小
设置字体,设置不同的 Typeface 就可以显示不同的字体。
paint.setTypeface(Typeface.DEFAULT);
canvas.drawText(text, 100, 150, paint);
是否使用伪粗体。
paint.setFakeBoldText(false);
canvas.drawText(text, 100, 150, paint);
paint.setFakeBoldText(true);
canvas.drawText(text, 100, 230, paint);
之所以叫伪粗体( fake bold ),因为它并不是通过选用更高 weight 的字体让文字 变粗,而是通过程序在运行时把文字给「描粗」了。
是否加删除线。
paint.setStrikeThruText(true);
canvas.drawText(text, 100, 150, paint);
是否加下划线。
paint.setUnderlineText(true);
canvas.drawText(text, 100, 150, paint);
设置文字横向错切角度。其实就是文字倾斜度的啦。
paint.setTextSkewX(-0.5f);
canvas.drawText(text, 100, 150, paint);
设置文字横向放缩。也就是文字变胖变瘦。
paint.setTypeface(Typeface.DEFAULT);
canvas.drawText(text, 100, 150, paint);
paint.setTypeface(Typeface.SERIF);
canvas.drawText(text, 100, 300, paint);
设置字符间距。默认值是 0。
paint.setLetterSpacing(0.2f);
canvas.drawText(text, 100, 150, paint);
为什么在默认的字符间距为 0 的情况下,字符和字符之间也没有紧紧贴着,这 个在前面 Canvas.drawText() 的 x 参数的时候已经说过了,每个字符的实际大小都会比显示出来的大
用 CSS 的 font-feature-settings 的方式来设置文字。
paint.setFontFeatureSettings("smcp"); // 设置 "small caps"
canvas.drawText("Hello HenCoder", 100, 150, paint);
CSS 全称是 Cascading Style Sheets ,是网页开发用来设置页面各种元素的样式 的。
设置文字的对齐方式。一共有三个值:LEFT CETNER 和 RIGHT。默认值为 LEFT 。
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);
设置绘制所使用的 Locale 。
Locale 直译是「地域」,其实就是在系统里设置的「语言」或「语言区域」,比如「简体中文(中国)」「English (US)」「English (UK)」。有些同源的语言,在文化发展过程中对一些相同的字衍生 出了不同的写法(比如中国大陆和日本对于某些汉字的写法就有细微差别。注意, 不是繁体和简体这种同音同义不同字,而真的是同样的一个字有两种写法)。系统 语言不同,同样的一个字的显示就有可能不同。
设置是否启用字体的 hinting (字体微调)。
现在的 Android 设备大多数都是是用的矢量字体。矢量字体的原理是对每个字体给 出一个字形的矢量描述,然后使用这一个矢量来对所有的尺寸的字体来生成对应的 字形。由于不必为所有字号都设计它们的字体形状,所以在字号较大的时候,矢量 字体也能够保持字体的圆润,这是矢量字体的优势。不过当文字的尺寸过小(比如 高度小于 16 像素),有些文字会由于失去过多细节而变得不太好看。 hinting 技术 就是为了解决这种问题的:通过向字体中加入 hinting 信息,让矢量字体在尺寸过小 的时候得到针对性的修正,从而提高显示效果。
不过在现在,手机屏幕的像素密度已经非常高,几乎不会再出现字体尺寸小到需要靠 hinting 来修正的情况,所以这个方法其 实没啥用了。可以忽略。
是否开启次像素级的抗锯齿( sub-pixel anti-aliasing )。
次像素级抗锯齿这个功能解释起来很麻烦,简单说就是根据程序所运行的设备的屏 幕类型,来进行针对性的次像素级的抗锯齿计算,从而达到更好的抗锯齿效果。更 详细的解释可以看这篇文章。 不过,和前面讲的字体 hinting 一样,由于现在手机屏幕像素密度已经很高,所以默 认抗锯齿效果就已经足够好了,一般没必要开启次像素级抗锯齿,所以这个方法基 本上没有必要使用。
不论是文字,还是图形或 Bitmap ,只有知道了尺寸,才能更好地确定应该摆放的 位置。由于文字的绘制和图形或 Bitmap 的绘制比起来,尺寸的计算复杂得多,所 以它有一整套的方法来计算文字尺寸。
获取推荐的行距。
即推荐的两行文字的 baseline 的距离。这个值是系统根据文字的字体和字号自动计 算的。它的作用是当你要手动绘制多行文字(而不是使用 StaticLayout)的时候, 可以在换行的时候给 y 坐标加上这个值来下移文字。
获取 Paint 的 FontMetrics 。 FontMetrics 是个相对专业的工具类,它提供了几个文字排印方面的数 值:ascent , descent , top , bottom , leading 。比较复杂,这里就不写了
-
2.2.3 getTextBounds(String text, int start, int end, Rect bounds)
-
获取文字的显示范围。
-
参数里,
- text 是要测量的文字
- start 和 end 分别是文字的起始和结束位 置
- bounds 是存储文字显示范围的对象,方法在测算完成之后会把结果写进 bounds 。
-
测量文字的宽度并返回。
和getTextBounds() 有什么不同?如果用代码分别使用 getTextBounds() 和 measureText() 来测量文字的宽 度,你会发现 measureText() 测出来的宽度总是比 getTextBounds() 大一点 点。这是因为这两个方法其实测量的是两个不一样的东西。
-
getTextBounds() 测量的是显示的宽度
-
measureText() 测量的是实际宽度
实际宽度比显示的宽
-
-
2.2.5 getTextWidths(String text, oat[] widths) canvas.drawText(texts[2], 100, 150 + paint.getFontSpacing * 2, paint)
获取字符串中每个字符的宽度,并把结果填入参数 widths 。
这相当于 measureText() 的一个快捷方法,它的计算等价于对字符串中的每个字 符分别调用 measureText() ,并把它们的计算结果分别填入 widths 的不同元 素。
-
这个方法也是用来测量文字宽度的。
但和 measureText() 的区别是, breakText() 是在给出宽度上限的前提下测量文字的宽度。如果文字的宽度超出了 上限,那么在临近超限的位置截断文字。
-
对于 EditText 以及类似的场景,会需要绘制光标。
-
2.2.7.1 getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)
对于一段文字,计算出某个字符处光标的 x 坐标。
start end 是文字的起始和结 束坐标; contextStart contextEnd 是上下文的起始和结束坐标;isRtl 是文字 的方向; offset 是字数的偏移,即计算第几个字符处的光标
-
2.2.7.2 getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, oat advance)
给出一个位置的像素值,计算出文字中最接近这个位置的字符偏移量(即第几个字 符最接近这个坐标)。
方法的参数很简单:
-
text 是要测量的文字;
-
start end 是文字的起始和结束坐 标;
-
contextStart contextEnd 是上下文的起始和结束坐标;
-
isRtl 是文字方 向;
-
advance 是给出的位置的像素值
填入参数,对应的字符偏移量将作为返回值 返回。
getOffsetForAdvance() 配合上 getRunAdvance() 一起使用,就可以实现「获 取用户点击处的文字坐标」的需求。
2020 7.5 16:12
-
-