自定义View drawText()绘制文字

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View drawText()绘制文字相关的知识,希望对你有一定的参考价值。

参考技术A Canvas 绘制文字的方式:drawText() drawTextRun() drawTextOnPath()

注:如果你从(0,0)点开始绘制Text,文字不会显示在View左上角,会显示在View的左上方。

这张图,电线上的小鸟,这里,电线就类似于文字的基线。

盗图:Hencoder

canvas.drawText()中,参数y,是指的文字的基线(baseLine)。参数x,也不是文字最左面的点,x的位置是文字起点靠左一点(这里是因为对于绝大多数字符,他们的宽度都要略微大于实际显示的宽度,字符的左右两边都会留出一部分间隙,用于文字间的间隔,因此我们设定绘制文字起点的时候 ,会发现实际文字绘制时候会靠右一点)。

类 似于drawText() 但是增加了两个设置----上下文---文字方向

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,从右向左)

沿着一条Path来绘制文字。
例:

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

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

staticLayout 是用canvas来绘制,但是不是canvas的方法。staticLayout一般用于绘制多行textView。如果你需要进行多行文字的绘制,并且对文字的排列和样式没有太复杂的花式要求,那么使用staticLayout最好。

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

例:

StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)

参数:
width 是文字区域的宽度,文字到达这个宽度后就会自动换行;
align 是文字的对齐方向;
spacingmult 是行间距的倍数,通常情况下填 1 就好;
spacingadd 是行间距的额外增加值,通常情况下填 0 就好;
includeadd 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。

设置文字大小

设置字体,字体可以是系统所有的字体,也可以是自己的字体。官方文档

这里的Typeface跟font是一个意思,都表示字体,但是,typerface指的是某套字体(font family),而font值指的是typeface具体的某个weight和size的分支。

是否使用伪粗体。

这种粗体叫做伪粗体,因为他不是设置更高的weight的字体让文字变粗,他是通过程序在运行时候将字体描粗。

是否添加删除线

是否添加下划线

#######2.1.6 setTextSkewX(float skewX)
设置文字的错切角度,通俗的说,就是设置文字倾斜

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

设置字符间距。默认为0。
注:setLetterSpacing为字符间距,setTextScaleX为文字横向宽度。

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

设置文字对其方式(LEFFT,CETNER,RIGHT 左中右,默认为左 既LEFT)。

设置绘制所使用的Locale,就是设置不同地域的语言(是汉语还是英语)

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

对于现在的手机(屏幕密度非常高),几乎不会出现字体尺寸小道需要修改hinting来修正的情况,所以这个方法,现在几乎不会用到。

设置是否开启文字的elegant height。
这个方法适用于有较高文字的语言。
把「大高个」文字的高度恢复为原始高度;
增大每行文字的上下边界,来容纳被加高了的文字。
中文不需要!!!

是否开启像素级抗锯齿
详细介绍

文字也有他自己的尺寸。
########2.2.1 float getFontsPACING()
获取推荐的行距

即推荐的两行文字的 baseline 的距离。这个值是系统根据文字的字体和字号自动计算的。它的作用是当你要手动绘制多行文字(而不是使用 StaticLayout)的时候,可以在换行的时候给 y 坐标加上这个值来下移文字。

获取Paint的FontMetrics

盗图:henCoder

如图,图中有两行文字,每一行都有 5 条线:top, ascent, baseline, descent, bottom。

• baseline: 上图中黑色的线。它的作用是作为文字显示的基准线。

• ascent / descent: 上图中绿色和橙色的线,它们的作用是限制普通字符的顶部和底部范围。
普通的字符,上不会高过 ascent ,下不会低过 descent ,例如上图中大部分的字形都显示在 ascent 和 descent 两条线的范围内。具体到 Android 的绘制中, ascent 的值是图中绿线和 baseline 的相对位移,它的值为负(因为它在 baseline 的上方); descent 的值是图中橙线和 baseline 相对位移,值为正(因为它在 baseline 的下方)。

• top / bottom: 上图中蓝色和红色的线,它们的作用是限制所有字形( glyph )的顶部和底部范围。
除了普通字符,有些字形的显示范围是会超过 ascent 和 descent 的,而 top 和 bottom 则限制的是所有字形的显示范围,包括这些特殊字形。例如上图的第二行文字里,就有两个泰文的字形分别超过了 ascent 和 descent 的限制,但它们都在 top 和 bottom 两条线的范围内。具体到 Android 的绘制中, top 的值是图中蓝线和 baseline 的相对位移,它的值为负(因为它在 baseline 的上方); bottom 的值是图中红线和 baseline 相对位移,值为正(因为它在 baseline 的下方)。

• leading: 这个词在上图中没有标记出来,因为它并不是指的某条线和 baseline 的相对位移。 leading 指的是行的额外间距,即对于上下相邻的两行,上行的 bottom 线和下行的 top 线的距离,也就是上图中第一行的红线和第二行的蓝线的距离(对,就是那个小细缝)。

leading 这个词的本意其实并不是行的额外间距,而是行距,即两个相邻行的 baseline 之间的距离。不过对于很多非专业领域,leading 的意思被改变了,被大家当做行的额外间距来用;而 Android 里的 leading ,同样也是行的额外间距的意思。leading 在这里应该读作 "ledding" 而不是 "leeding" 。

FontMetrics 提供的就是 Paint 根据当前字体和字号,得出的这些值的推荐值。它把这些值以变量的形式存储,供开发者需要时使用。

FontMetrics.ascent:float 类型。
FontMetrics.descent:float 类型。
FontMetrics.top:float 类型。
FontMetrics.bottom:float 类型。
FontMetrics.leading:float 类型。
另外,ascent 和 descent 这两个值还可以通过 Paint.ascent() 和 Paint.descent() 来快捷获取。

FontMetrics 和 getFontSpacing();
从定义可以看出,上图中两行文字的 font spacing (即相邻两行的 baseline 的距离) 可以通过 bottom - top + leading (top 的值为负,前面刚说过,记得吧?)来计算得出。

实际 bottom - top + leading 的结果是要大于 getFontSpacing() 的返回值的。

两个方法计算得出的 font spacing 竟然不一样?

这并不是 bug,而是因为 getFontSpacing() 的结果并不是通过 FontMetrics 的标准值计算出来的,而是另外计算出来的一个值,它能够做到在两行文字不显得拥挤的前提下缩短行距,以此来得到更好的显示效果。所以如果你要对文字手动换行绘制,多数时候应该选取 getFontSpacing() 来得到行距,不但使用更简单,显示效果也会更好。

getFontMetrics() 的返回值是 FontMetrics 类型。它还有一个重载方法 getFontMetrics(FontMetrics fontMetrics) ,计算结果会直接填进传入的 FontMetrics 对象,而不是重新创建一个对象。这种用法在需要频繁获取 FontMetrics 的时候性能会好些。

另外,这两个方法还有一对同样结构的对应的方法 getFontMetricsInt() 和 getFontMetricsInt(FontMetricsInt fontMetrics) ,用于获取 FontMetricsInt 类型的结果。

推荐:计算基线

获取文字的显示范围,text为要测量的文字,start为文字起始的位置,end为文字介素的位子,bounds存储文字显示范围的对象,在测量结束以后会将结果写入bounds.

(他还有一个重载方法 getTextBounds(char[] text, int index, int count, Rect bounds) 用法相似)

测量文字宽度并返回

注:measure(String text) 测量后的宽度比getTextBounds()测量后的值大一点
• getTextBounds: 它测量的是文字的显示范围(关键词:显示)。形象点来说,你这段文字外放置一个可变的矩形,然后把矩形尽可能地缩小,一直小到这个矩形恰好紧紧包裹住文字,那么这个矩形的范围,就是这段文字的 bounds。

• measureText(): 它测量的是文字绘制时所占用的宽度(关键词:占用)。前面已经讲过,一个文字在界面中,往往需要占用比他的实际显示宽度更多一点的宽度,以此来让文字和文字之间保留一些间距,不会显得过于拥挤。上面的这幅图,我并没有设置 setLetterSpacing() ,这里的 letter spacing 是默认值 0,但你可以看到,图中每两个字母之间都是有空隙的。另外,下方那条用于表示文字宽度的横线,在左边超出了第一个字母 H 一段距离的,在右边也超出了最后一个字母 r(虽然右边这里用肉眼不太容易分辨),而就是两边的这两个「超出」,导致了 measureText() 比 getTextBounds() 测量出的宽度要大一些。

#######2.2.5 getTextWidths(String text,float[] widths)
获取字符串中每个字符的宽度,并把结果填入参数 widths。

这相当于 measureText() 的一个快捷方法,它的计算等价于对字符串中的每个字符分别调用 measureText() ,并把它们的计算结果分别填入 widths 的不同元素。

这个方法也是用来测量文字宽度的。但和 measureText() 的区别是, breakText() 是在给出宽度上限的前提下测量文字的宽度。如果文字的宽度超出了上限,那么在临近超限的位置截断文字。
注:是在给出的宽度范围下测量文字宽度,如果文字宽度超出上限,在临近超限位置截断文字。
breakText的返回值是截取的文字个数。常用语多行文字的折行计算。

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 是字数的偏移,即计算第几个字符处的光标。
例:

其实,说是测量光标位置的,本质上这也是一个测量文字宽度的方法。上面这个例子中,start 和 contextStart 都是 0, end contextEnd 和 offset 都等于 text.length()。在这种情况下,它是等价于 measureText(text) 的,即完整测量一段文字的宽度。而对于更复杂的需求,getRunAdvance() 能做的事就比 measureText() 多了。

例:

如上图,🇨🇳 虽然占了 4 个字符(\uD83C\uDDE8\uD83C\uDDF3),但当 offset 是表情中间处时, getRunAdvance() 得出的结果并不会在表情的中间处。

2.2.7.2 getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)
给出一个位置的像素值,计算出文字中最接近这个位置的字符偏移量(即第几个字符最接近这个坐标)。

参数:
text 是要测量的文字;
start end 是文字的起始和结束坐标;
contextStart contextEnd 是上下文的起始和结束坐标;
isRtl 是文字方向;
advance 是给出的位置的像素值。填入参数,对应的字符偏移量将作为返回值返回。

getOffsetForAdvance() 配合上 getRunAdvance() 一起使用,就可以实现「获取用户点击处的文字坐标」的需求。

#######2.2.8 hasGlyph(String string)
检查指定的字符串中是否是一个单独的字形 (glyph)。最简单的情况是,string 只有一个字母(比如 a)。

android自定义文字绘制

参考技术A 最近在学习自定义view中的文本绘制,以下为学习期间快速总结笔记:
1.文字绘制使用canvas.drawText()
2.文字绘制的坐标起点默认为文字左下角,横向右侧为正,纵向往上为正,坐标即为BaseLine文本基线
3.文本结构一共有五条线,从上往下分别为Top,acenset,basline,decenset,bottom
4.文本绘制坐标,正常的绘制文字高度建议使用fontMetrics获取从baseline到top的距离,或者使用textbounds的top到bottom的距离,
a.若绘制的为静态文字,建议使用textBounds,这样视觉上会美观一些
b.若为动态,如:倒计时的文字,建议使用从baseline到top的距离
c.若想让文字贴view的边,尽可能的减小文字与view的间隔,建议使用textbounds设置坐标
d.若多行文字,每行文字size差别较大,x坐标建议使用textBounds的left设置,可以尽可能的减小每行首字母纵向无法对齐的问题
5.若绘制多行文字,可以使用TextPaint进行绘制,系统会根据文本内容自动折行
6.若需要手动设置折行,使用api:breakText来判断折行位置,使用此方法可以实现图文混排功能

以上是关于自定义View drawText()绘制文字的主要内容,如果未能解决你的问题,请参考以下文章

Canvas drawText文字垂直居中方案

drawText注意事项

android canvas drawText()文字居中

Android 自定义 View 知识点

在继承 View 的自定义视图中添加一个 TextView

06自定义View之文字绘制