Canvas DrawText详解_canvas

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

↑ 这里没有贴错图哦

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再附上一张图,应该能更清楚地表达:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是为什么?为什么其它的 Canvas.drawXXX() 方法,都是以左上角作为基准点的,而 drawText() 却是文字左下方?

先别觉得日了狗,这种设计其实是有道理的。drawText() 参数中的 y ,指的是文字的**基线( baseline )**的位置。也就是这条线:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

众所周知,不同的语言和文字,每个字符的高度和上下位置都是不一样的。要让不同的文字并排显示的时候整体看起来稳当,需要让它们上下对齐。但这个对齐的方式,不能是简单的「底部对齐」或「顶部对齐」或「中间对齐」,而应该是一种类似于「重心对齐」的方式。就像电线上的小鸟一样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每只小鸟的最高点和最低点都不一样,但画面很平衡

而这个用来让所有文字互相对齐的基准线,就是基线( baseline )。 drawText() 方法参数中的 y 值,就是指定的基线的位置。

说完 y 值,再说说 x 值。从前面图中的标记可以看出来,「Hello HenCoder」绘制出来之后的 x 点并不是字母 “H” 左边的位置,而是比它的左边再往左一点点。那么这个「往左的一点点」是什么呢?

它是字母 “H” 的左边的空隙。绝大多数的字符,它们的宽度都是要略微大于实际显示的宽度的。字符的左右两边会留出一部分空隙,用于文字之间的间隔,以及文字和边框的间隔。就像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用竖线标记出边界后的文字。

所以,明白为什么 x 坐标在 “H” 的左边再往左一点点的位置,而不是紧贴着 “H” 的左边线了吗?就是因为 “H” 的这个留出的空隙。

除了 drawText(text, x, y, paint) 之外, drawText() 还有几个重载方法,使用方式跟这个都差不多,我就不说了,你自己看吧。

1.2 drawTextRun()

声明:这个方法对中国人没用。所以如果你有兴趣,可以继续看;而如果你想省时间,直接跳过这个方法看后面的就好了,没有任何毒副作用。

drawTextRun() 是在 API 23 新加入的方法。它和 drawText() 一样都是绘制文字,但加入了两项额外的设置——上下文和文字方向——用于辅助一些文字结构比较特殊的语言的绘制。

  • 额外设置一:上下文。

有些语言的文字,字符的形状会互相之间影响:一个字你单独写是一个样,和别的字放在一起写又是另外一个样。不过由于我们最熟悉的语言——汉语和英语——都没有这种情况,所以只靠说可能不太好理解,我就用图说明一下吧。

以阿拉伯文为例。阿拉伯文里的「عربى(阿拉伯)」是一个四字词,它的中间两个字符「رب」在这个词里的样子,和单独写的时候的样子是不同的。也就是说,当这四个字写在一起的时候,中间两个字由于受到两边的字的影响,形状被改变了。看图吧:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面第二行和第三行的文字是完全一样的俩字,你敢信?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

哇塞,是不是特别神奇?

不过我们就不用管它为什么这么神奇了,也不用替阿拉伯人操心这么复杂的文字他们使用起来会不会很痛苦,人家都已经用了几百上千年了。我还说回到 drawTextRun()。 drawTextRun() 除了文字的内容和位置之外,还可以设置文字的上下文(也就是要绘制的文字的左边和右边是什么文字,虽然这些文字并不会被绘制出来),从而让同样的文字可以按需表现出不同的显示效果。

  • 额外设置二:文字方向。

除了上下文, drawTextRun() 还可以设置文字的方向,即文字是从左到右还是从右到左排列的。

介绍完这两类额外设置,来看一下具体的方法吧:

drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)

参数: 
text:要绘制的文字 
start:从那个字开始绘制 
end:绘制到哪个字结束 
contextStart:上下文的起始位置。contextStart 需要小于等于 start 
contextEnd:上下文的结束位置。contextEnd 需要大于等于 end 
x:文字左边的坐标 
y:文字的基线坐标 
isRtl:是否是 RTL(Right-To-Left,从右向左)

要实现上面图中的「同样的字有不同的显示」效果,调节 contextStart 和 contextEnd 就可以了,至于具体的实现,你有兴趣的话就自己试试吧。

这就是 drawTextRun() ,一个增加了「上下文」和「RTL」支持的增强版本的 drawText() 。不过就像刚才说过的,这个方法对中国人其实没什么用……

1.3 drawTextOnPath()

沿着一条 Path 来绘制文字。这是一个耍杂技的方法。

canvas.drawPath(path, paint); // 把 Path 也绘制出来,理解起来更方便  
canvas.drawTextOnPath("Hello HeCoder", path, 0, 0, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

吁,拐角处的文字怎么那么难看?

所以记住一条原则: drawTextOnPath() 使用的 Path ,拐弯处全用圆角,别用尖角。

具体的方法很简单:

drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

参数里,需要解释的只有两个: hOffset 和 vOffset。它们是文字相对于 Path 的水平偏移量和竖直偏移量,利用它们可以调整文字的位置。例如你设置 hOffset 为 5, vOffset 为 10,文字就会右移 5 像素和下移 10 像素。

1.4 StaticLayout

额外讲一个 StaticLayout。这个也是使用 Canvas 来进行文字的绘制,不过并不是使用 Canvas 的方法。

Canvas.drawText() 只能绘制单行的文字,而不能换行。它:

  • 不能在 View 的边缘自动折行
canvas.drawPath(path, paint); // 把 Path 也绘制出来,理解起来更方便  
canvas.drawTextOnPath("Hello HeCoder", path, 0, 0, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

到了 View 的边缘处,文字继续向后绘制到看不见的地方,而不是自动换行

  • 不能在换行符 \n 处换行
  String text = "a\nbc\ndefghi\njklm\nnopqrst\nuvwx\nyz";

  ...

  canvas.drawText(text, 50, 100, paint);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在换行符 \n 的位置并没有换行,而只是加了个空格

如果需要绘制多行的文字,你必须自行把文字切断后分多次使用 drawText() 来绘制,或者——使用 StaticLayout 。

StaticLayout 并不是一个 View 或者 ViewGroup ,而是 android.text.Layout 的子类,它是纯粹用来绘制文字的。 StaticLayout 支持换行,它既可以为文字设置宽度上限来让文字自动换行,也会在 \n 处主动换行。

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);
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();  

上面代码中出现的 Canvas.save() Canvas.translate() 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 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。

如果你需要进行多行文字的绘制,并且对文字的排列和样式没有太复杂的花式要求,那么使用 StaticLayout 就好。

2 Paint 对文字绘制的辅助

Paint 对文字绘制的辅助,有两类方法:设置显示效果的和测量文字尺寸的。

2.1 设置显示效果类

2.1.1 setTextSize(float textSize)

设置文字大小。

paint.setTextSize(18);  
canvas.drawText(text, 100, 25, paint);  
paint.setTextSize(36);  
canvas.drawText(text, 100, 70, paint);  
paint.setTextSize(60);  
canvas.drawText(text, 100, 145, paint);  
paint.setTextSize(84);  
canvas.drawText(text, 100, 240, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很简单,不再详细解释。

2.1.2 setTypeface(Typeface typeface)

设置字体。

paint.setTextSize(18);  
canvas.drawText(text, 100, 25, paint);  
paint.setTextSize(36);  
canvas.drawText(text, 100, 70, paint);  
paint.setTextSize(60);  
canvas.drawText(text, 100, 145, paint);  
paint.setTextSize(84);  
canvas.drawText(text, 100, 240, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

设置不同的 Typeface 就可以显示不同的字体。我们中国人谈到「字体」,比较熟悉的词是 font, typeface 和 font 是一个意思,都表示字体。 Typeface 这个类的具体用法,需要了解的话可以直接看文档,很简单。

严格地说,其实 typeface 和 font 意思不完全一样。typeface 指的是某套字体(即 font family ),而 font 指的是一个 typeface 具体的某个 weight 和 size 的分支。不过无所谓啦~做人最紧要系开心啦。

2.1.3 setFakeBoldText(boolean fakeBoldText)

是否使用伪粗体。

paint.setFakeBoldText(false);  
canvas.drawText(text, 100, 150, paint);  
paint.setFakeBoldText(true);  
canvas.drawText(text, 100, 230, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

之所以叫伪粗体( fake bold ),因为它并不是通过选用更高 weight 的字体让文字变粗,而是通过程序在运行时把文字给「描粗」了。

2.1.4 setStrikeThruText(boolean strikeThruText)

是否加删除线。

paint.setStrikeThruText(true);  
canvas.drawText(text, 100, 150, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.5 setUnderlineText(boolean underlineText)

是否加下划线。

paint.setUnderlineText(true);  
canvas.drawText(text, 100, 150, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.6 setTextSkewX(float skewX)

设置文字横向错切角度。其实就是文字倾斜度的啦。

paint.setTextSkewX(-0.5f);  
canvas.drawText(text, 100, 150, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.7 setTextScaleX(float scaleX)

设置文字横向放缩。也就是文字变胖变瘦。

paint.setTextScaleX(1);  
canvas.drawText(text, 100, 150, paint);  
paint.setTextScaleX(0.8f);  
canvas.drawText(text, 100, 230, paint);  
paint.setTextScaleX(1.2f);  
canvas.drawText(text, 100, 310, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.8 setLetterSpacing(float letterSpacing)

设置字符间距。默认值是 0。

paint.setLetterSpacing(0.2f);  
canvas.drawText(text, 100, 150, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么在默认的字符间距为 0 的情况下,字符和字符之间也没有紧紧贴着,这个我在前面讲 Canvas.drawText() 的 x 参数的时候已经说过了,在这里应该没有疑问吧?

2.1.9 setFontFeatureSettings(String settings)

用 CSS 的 font-feature-settings 的方式来设置文字。

paint.setFontFeatureSettings("smcp"); // 设置 "small caps"  
canvas.drawText("Hello HenCoder", 100, 150, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CSS 全称是 Cascading Style Sheets ,是网页开发用来设置页面各种元素的样式的。咦,网页开发的设置怎么会出现在 Android 的 API 里?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

大多数 Android 开发者都不了解这个 CSS 的 font-feature-settings 属性,不过没关系,这个属性设置的都是文字的一些次要特性,所以不用着急了解这个方法。当然有兴趣的话也可以看一看哈,文档在这里

2.1.10 setTextAlign(Paint.Align align)

设置文字的对齐方式。一共有三个值: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);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.11 setTextLocale(Locale locale) / setTextLocales(LocaleList locales)

设置绘制所使用的 Locale

Locale 直译是「地域」,其实就是你在系统里设置的「语言」或「语言区域」(具体名称取决于你用的是什么手机),比如「简体中文(中国)」「English (US)」「English (UK)」。有些同源的语言,在文化发展过程中对一些相同的字衍生出了不同的写法(比如中国大陆和日本对于某些汉字的写法就有细微差别。注意,不是繁体和简体这种同音同义不同字,而真的是同样的一个字有两种写法)。系统语言不同,同样的一个字的显示就有可能不同。你可以试一下把自己手机的语言改成日文,然后打开微信看看聊天记录,你会明显发现文字的显示发生了很多细微的变化,这就是由于系统的 Locale 改变所导致的。

Canvas 绘制的时候,默认使用的是系统设置里的 Locale。而通过 Paint.setTextLocale(Locale locale) 就可以在不改变系统设置的情况下,直接修改绘制时的 Locale

paint.setTextLocale(Locale.CHINA); // 简体中文  
canvas.drawText(text, 150, 150, paint);  
paint.setTextLocale(Locale.TAIWAN); // 繁体中文  
canvas.drawText(text, 150, 150 + textHeight, paint);  
paint.setTextLocale(Locale.JAPAN); // 日语  
canvas.drawText(text, 150, 150 + textHeight * 2, paint);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有意思吧?

另外,由于 Android 7.0 ( API v24) 加入了多语言区域的支持,所以在 API v24 以及更高版本上,还可以使用 setTextLocales(LocaleList locales) 来为绘制设置多个语言区域。

2.1.12 setHinting(int mode)

设置是否启用字体的 hinting (字体微调)。

现在的 Android 设备大多数都是是用的矢量字体。矢量字体的原理是对每个字体给出一个字形的矢量描述,然后使用这一个矢量来对所有的尺寸的字体来生成对应的字形。由于不必为所有字号都设计它们的字体形状,所以在字号较大的时候,矢量字体也能够保持字体的圆润,这是矢量字体的优势。不过当文字的尺寸过小(比如高度小于 16 像素),有些文字会由于失去过多细节而变得不太好看。 hinting 技术就是为了解决这种问题的:通过向字体中加入 hinting 信息,让矢量字体在尺寸过小的时候得到针对性的修正,从而提高显示效果。效果图盗一张维基百科的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

功能很强,效果很赞。不过在现在( 2017 年),手机屏幕的像素密度已经非常高,几乎不会再出现字体尺寸小到需要靠 hinting 来修正的情况,所以这个方法其实……没啥用了。可以忽略。

2.1.13 setElegantTextHeight(boolean elegant)

声明:这个方法对中国人没用,不想看的话可以直接跳过,无毒副作用。

设置是否开启文字的 elegant height 。开启之后,文字的高度就变优雅了(误)。下面解释一下所谓的 elegant height:

在有些语言中,可能会出现一些非常高的字形:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

左边那几个泰文文字,挺高的吧?但其实它们已经是被压缩过了的,它们本来比这还要高。

这些比较高的文字,通常都有两个版本的字体:一个原始版本,一个压缩了高度的版本。压缩版本可以保证让这些「大高个」文字在和普通文字(例如拉丁文字)放在一起的时候看起来不会显得太奇怪。事实上,Paint 绘制文字时是用的默认版本就是压缩版本,就像上图这样。

不过有的时候,开发者会需要使用它们的原始(优雅)版本。使用 setElegantTextHeight() 就可以切换到原始版本:

paint.setElegantTextHeight(true);  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这字得有多高?2 米 26 ?

那么,setElegantTextHeight() 的作用到这里就很清晰了:

  1. 把「大高个」文字的高度恢复为原始高度;
  2. 增大每行文字的上下边界,来容纳被加高了的文字。

其实这个问题我已经在 stackoverflow 回答过一次,原回答在这里

不过就像前面说的,由于中国人常用的汉语和英语的文字并不会达到这种高度,所以这个方法对于中国人基本上是没用的。

2.1.14 setSubpixelText(boolean subpixelText)

是否开启次像素级的抗锯齿( sub-pixel anti-aliasing )。

次像素级抗锯齿这个功能解释起来很麻烦,简单说就是根据程序所运行的设备的屏幕类型,来进行针对性的次像素级的抗锯齿计算,从而达到更好的抗锯齿效果。更详细的解释可以看这篇文章

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

中国人常用的汉语和英语的文字并不会达到这种高度,所以这个方法对于中国人基本上是没用的。

2.1.14 setSubpixelText(boolean subpixelText)

是否开启次像素级的抗锯齿( sub-pixel anti-aliasing )。

次像素级抗锯齿这个功能解释起来很麻烦,简单说就是根据程序所运行的设备的屏幕类型,来进行针对性的次像素级的抗锯齿计算,从而达到更好的抗锯齿效果。更详细的解释可以看这篇文章

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-GyBwxKCn-1715863417845)]
[外链图片转存中…(img-Oj193ZxQ-1715863417845)]

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值