Android 在任意位置绘制文本

基础

通常情况下,在屏幕的特定位置上显示文字是个很简单的事情。使用TextView,结合各种XxxLayout,基本上想在哪显示文字都可以。但当显示的文字需要频繁更新的时候,使用TextView可能就不是那么明智了。

前段时间遇到这样一个需求,如图:

外围圈圈旋转填充的过程中,中间的数字(指代百分比)从0到100变化,动画在几百毫秒内完成。文字在圈圈的正中显示。看到需求,首先想到的自然是使用TextView来显示中间的数字,通过不断setText来更新文本显示。然而,运行起来后发现TextView的更新有很严重的卡顿,打开TextView#onDraw方法,发现这个方法里做了很多事情,onDraw如此频繁地被调用,卡顿是自然的。如果直接继承ViewonDraw时使用Canvas#drawText实现文本绘制,省去TextView的大量额外计算,效率则会提升很多。

Canvas#drawText的原型如下:

public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

绘制文本的时候,我们需要传入(x,y)坐标参数让Canvas知道我们期望在哪个位置绘制文本。那么问题来了,(x,y)到底是哪个点呢?x、y分别传入多少才能让文字在圈圈的中间显示呢?本文将通过这个例子,来讲述Android中如何灵活地在想要的位置绘制文本。

上述需求中,如果我们能找到文本的中心点和(x, y)的关系,然后把这个中心点和圈圈的中心点对齐,算出相应的(x, y),文本就能显示在圈圈的中心了。

首先通过如下实例代码来观察文本位置和(x,y)坐标的关系:

String text = "afp8";
canvas.drawText(text, x, y, paint);
// 画两条垂直相交的直线直观地展示点(x,y)的位置
drawHorizontalLine(canvas, y, Color.BLUE); // 自定义方法,画一条水平线
drawVerticalLine(canvas, x, Color.BLUE);   // 自定义方法,画一条垂直线

运行结果如下:

由此看到,(x,y)是文本区域左下角的一个点,x值是文本区域的左边沿,y水平线对齐”a” “f” “8” 的底部,但”p”有一部分超出了y水平线。实际上,y水平线就是字体排印学中的“基线(baseline)”,大部分英文字母和阿拉伯数字都绘制在基线之上,例外的如上述实例中的“p”等,下半部分会超出基线。基线以下的部分叫做“降部(descent)”,相应地,在基线之上的部分称为“升部(ascent)”Paint类提供了Paint#descentPaint#ascent方法获取文本的降部和升部。以下实例代码结合降部和升部,画出两条水平线:

drawHorizontalLine(canvas, y + paint.descent(), Color.GREEN);
drawHorizontalLine(canvas, y + paint.ascent(), Color.RED); // paint.ascent()返回负数,因此是"+"

运行结果如下:

通过升部降部,我们计算出了文本绘制区域的上下边沿,上下边沿y坐标的中值就是文本中心点的y坐标。中心点的y坐标已经可以计算出来,那x坐标呢?我们可以想到,既然已经知道文本区域左边沿,只要知道文本区域的宽度,左边向右平移半个宽度,就能得出中心点的x坐标。试一下:

float textWidth = paint.measureText("afp8");
drawVerticalLine(canvas, x + textWidth / 2, Color.BLACK);

运行结果如下:

利用文本宽度,我们也顺利计算出文本区域中心点的x坐标。因此,上述需求也就可以轻松实现了。

扩展

Paint#setTextAlign

上述实例中,要找到文本区域中心点的x坐标,实际上还有更简单的实现方式,就是设置画笔的对齐方式为Paint.Align.CenterPaint#setTextAlign作用是设置画笔绘制文本时(x,y)参考点的水平对齐方式,可以是Paint.Align.LEFTPaint.Align.CENTERPaint.Align.Right(默认是LEFT),三者的区别可通过以下实例代码来体现:

drawHorizontalLine(canvas, y, Color.BLUE);

paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("left", x, y, paint);
drawVerticalLine(canvas, x, Color.BLUE);

paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("center", x + 550, y, paint);
drawVerticalLine(canvas, x + 550, Color.RED);

paint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText("right", x + 1200, y, paint);
drawVerticalLine(canvas, x + 1200, Color.GREEN);

运行结果如下:

Paint#getTextBounds VS. Paint#measureText

根据文档,Paint#getTextBounds可以获取一个能包裹住文本的最小的矩形,矩形原点默认是(0,0)。参考以下实例代码,并将Paint#getTextBoundsPaint#measureText获取的文本宽度进行比较:

paint.getTextBounds(text, 0, text.length(), rect);
// 获取的矩形的原点是(0,0),加100和加300是为了让矩形的左上角和文本的左上角对齐
canvas.drawRect(rect.left + 100, rect.top + 300,
    rect.right + 100, rect.bottom + 300, paint);
float textWidth = paint.measureText(text);
drawVerticalLine(canvas, x + textWidth, Color.GREEN);

运行结果如下:

如文档所说,Paint#getTextBounds得到的矩形是能包裹文本的最小的矩形,对齐后矩形的四边都紧贴着文本。而Paint#measureText获取的文本宽度实际上比Paint#getTextBounds得到的矩形宽度要大。而且标注文本区域的升部和降部的两条水平线间的距离比Paint#getTextBounds得到的矩形的高度也要大一些。

中文字符

维基百科说东亚字体无基线,也无升部和降部,那Android里中文的绘制是怎样的一种情况呢?先看一个中文字符绘制的实例:

canvas.drawText("中文", x, y, paint);
drawHorizontalLine(canvas, y + paint.descent(), Color.GREEN);
drawHorizontalLine(canvas, y + paint.ascent(), Color.BLACK);

运行结果如下:

从运行结果看,似乎和英文字符的绘制并无两样,好像也是有“基线”和“升部”“降部”的。那怎么理解“东亚字体无基线”呢?这里直接贴一个来自知乎用户的解释

总结

  • 使用Canvas#drawText进行文本绘制时,参考点(x,y)的x坐标根据画笔的对齐方式而定,可以通过Paint#setTextAlign设置左、中、右对齐。而y坐标是基线的y坐标。
  • 使用Paint#ascentPaint#descent获取文本区域的升部和降部,进而可以定位文本区域的上下边沿。
  • Paint#getTextBounds获取一个能包裹住文本的最小矩形,矩形原点默认为(0,0)。
  • 中文字符的绘制和英文字符并无区别,也可使用类似的基线和升部、降部。

文章来源于互联网:Android 在任意位置绘制文本

下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/21817,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?