前言

自定义View——Paint画笔 中,效果方法提到了设置文本相关的方法,这里是对其的补充。
关于文本的API,具体如图所示:



android Paint 文字 行间距 paintnet修改文字_自定义view 


使用

对于文本相关的API,有很多,使用也很简单,这里简单罗列一下:

setTextSize(float textSize)	                                     设置文字大小
setUnderlineText(boolean underlineText)	                         设置下划线
setStrikeThruText(boolean strikeThruText)	                     设置删除线
setFakeBoldText(boolean fakeBoldText)	                         设置文本粗体
setTextSkewX(float skewX)	                                     设置文本水平的倾斜度,值可为负数(向右斜)、正数(向左斜)
setTextScaleX(float scaleX)	                                     设置水平方向缩放
setLetterSpacing(float letterSpacing)                            设置文字间隔
setShadowLayer(float radius, float dx, float dy, int shadowColor)设置阴影
setTypeface(Typeface typeface)	                                 设置文本的字体

以上这些方法,使用很简单,这里不详细介绍了,这里给个效果图:



android Paint 文字 行间距 paintnet修改文字_自定义view _02


setTypeface(Typeface typeface)

用于设置字体,其实 Typeface 包括字体与样式。可以使用系统自带的样式,也可以自定义。

获取Typeface 对象的方式:

Typeface defaultFromStyle(int style)                    //创建默认字体(宋体)
Typeface create(Typeface family, int style)             //通过其它Typeface变量来构建文字样式(常用)
Typeface create(String familyName, int style)           //直接通过指定字体名来加载系统中自带的文字样式
Typeface createFromAsset(AssetManager mgr, String path) //通过从Asset中获取外部字体来显示字体样式
Typeface createFromFile(String path)                    //直接从路径创建
Typeface createFromFile(File path)                      //从外部路径来创建字体样式

参数:
family:Typeface 类型,指定该类已定义的字体,有:

Typeface.DEFAULT          //常规字体类型
Typeface.DEFAULT_BOLD     //黑体字体类型
Typeface.MONOSPACE        //等宽字体类型
Typeface.SANS_SERIF       //sans serif字体类型
Typeface.SERIF            //serif字体类型

familyName:指定系统字体类型,如:“宋体”、"微软雅黑"等

style:指的是样式,常用的字体样式有:

Typeface.BOLD        //粗体
Typeface.BOLD_ITALIC //粗斜体
Typeface.ITALIC      //斜体
Typeface.NORMAL      //常规

使用:

1.系统字体

mTestPaint.setTextSize(40);

//直接设置Typeface对象
mTestPaint.setTypeface(Typeface.MONOSPACE);
canvas.drawText("字体monospace,样式默认", 50, 100, mTestPaint);


//指定样式
Typeface typeface0 = Typeface.defaultFromStyle(Typeface.ITALIC);
mTestPaint.setTypeface(typeface0);
canvas.drawText("字体默认,样式斜体", 50, 150, mTestPaint);

//指定字体、样式
Typeface typeface1 = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC);
mTestPaint.setTypeface(typeface1);
canvas.drawText("字体sans serif,样式粗体+斜体", 50, 200, mTestPaint);

//指定字体、样式
Typeface typeface2 = Typeface.create("微软雅黑", Typeface.BOLD);
mTestPaint.setTypeface(typeface2);
canvas.drawText("字体微软雅黑,样式粗体", 50, 250, mTestPaint);

mTestPaint.setTextSize(40);

//直接设置Typeface对象
mTestPaint.setTypeface(Typeface.MONOSPACE);
canvas.drawText("字体monospace,样式默认", 50, 100, mTestPaint);


//指定样式
Typeface typeface0 = Typeface.defaultFromStyle(Typeface.ITALIC);
mTestPaint.setTypeface(typeface0);
canvas.drawText("字体默认,样式斜体", 50, 150, mTestPaint);

//指定字体、样式
Typeface typeface1 = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC);
mTestPaint.setTypeface(typeface1);
canvas.drawText("字体sans serif,样式粗体+斜体", 50, 200, mTestPaint);

//指定字体、样式
Typeface typeface2 = Typeface.create("微软雅黑", Typeface.BOLD);
mTestPaint.setTypeface(typeface2);
canvas.drawText("字体微软雅黑,样式粗体", 50, 250, mTestPaint);

2.自定义字体
一般使用createFromAsset()方法,后面2种就不介绍了。
首先,在main目录下新建asset文件夹,然后在asset目录下新建font文件夹,最后将格式为.ttf的字体放置该目录,就可以开始使用了。

//该字体对中文没有效果
Typeface typeface3 = Typeface.createFromAsset(getContext().getAssets(),"fonts/samplefont.ttf");
mTestPaint.setTypeface(typeface3);
canvas.drawText("Custom Font", 50, 300, mTestPaint);

//该字体对中文没有效果
Typeface typeface3 = Typeface.createFromAsset(getContext().getAssets(),"fonts/samplefont.ttf");
mTestPaint.setTypeface(typeface3);
canvas.drawText("Custom Font", 50, 300, mTestPaint);

效果图:



android Paint 文字 行间距 paintnet修改文字_文本居中_03


setTextAlign(Paint.Align align)

设置文字对齐方式,这个方法,看起来貌似也是很简单。但要是实际用起来,可能有点懵了,看代码:

private void testTextAlign(Canvas canvas){
canvas.translate(getWidth()/2, 200);//先将画布平移,方便观察

mTestPaint.reset();
mTestPaint.setTextSize(60);
mTestPaint.setStrokeWidth(8);

mTestPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("左对齐的文本", 0, 0, mTestPaint);

mTestPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("居中对齐的文本", 0, 100, mTestPaint);

mTestPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText("右对齐的文本", 0, 200, mTestPaint);

private void testTextAlign(Canvas canvas){
canvas.translate(getWidth()/2, 200);//先将画布平移,方便观察

mTestPaint.reset();
mTestPaint.setTextSize(60);
mTestPaint.setStrokeWidth(8);

mTestPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("左对齐的文本", 0, 0, mTestPaint);

mTestPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("居中对齐的文本", 0, 100, mTestPaint);

mTestPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText("右对齐的文本", 0, 200, mTestPaint);



android Paint 文字 行间距 paintnet修改文字_自定义view _04

这样的效果,跟我们平时布局不一样,好像反了。 是的,这里的相对位置不是指文本绘制的起点,而是指的绘制文字所在矩形的相对位置(图中的红框)。注意这一点就可以了。

setFontFeatureSettings(String settings)

使用CSS的格式设置字体,具体可以参考CSS文档

setTextLocale(Locale locale)

通过地域设置字体

setTextLocales(LocaleList locales)

在API24添加的,android7.0可以设置多语言列表,通过locales的顺序设置字体


不常用:(建议看看就好,没必要研究,信我!)

setHinting(int mode)

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

setSubpixelText(boolean subpixelText)

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

setLinearText(boolean linearText)

设置设置是否打开线性文本标识
以上三个方法,主要是针对以前设备低像素、低内存,低性能,目前来说,已经用不到,也很少用。

setElegantTextHeight(boolean elegant)

设置优雅(原始)高度 。默认时,字体是压缩之后才显示的,如果想显示原始的高度,就可以设置为true。不过,该方法对于中文没什么用,一般用于特殊的字符


以上方法,绘制文本应该没什么大问题。
但,还有一些误区,我们需要注意下。(也是通过这次总结才知道)
先来看一个demo:

mTestPaint.setTextSize(75);
canvas.drawText("原点为(50,50)的文本", 50, 50, mTestPaint);

mTestPaint.setTextSize(40);
canvas.drawText("在屏幕中央的文本", getWidth()/2, getHeight()/2, mTestPaint);


mTestPaint.setTextSize(75);
canvas.drawText("原点为(50,50)的文本", 50, 50, mTestPaint);

mTestPaint.setTextSize(40);
canvas.drawText("在屏幕中央的文本", getWidth()/2, getHeight()/2, mTestPaint);

最后效果图:



android Paint 文字 行间距 paintnet修改文字_基线_05


这个效果图,应该会疑惑:
问题1:

canvas.drawText("原点为(50,50)的文本", 50, 50, mTestPaint);

传进去的原点坐标(50,50), 为什么字体会显示在上面,被挡住?(红点是坐标)

问题2:

canvas.drawText("在屏幕中央的文本", getWidth()/2, getHeight()/2, mTestPaint);

传进去的原点坐标是屏幕中心点,而且文字对齐方式也是居中,但最终垂直方向还是没有居中。

这里引出2个知识点:
文本坐标具体的是什么?(基线)
如何让文本居中显示?(FontMetrics测量类)

小时候,写字母,必须在四线格规定的格子写。文本的绘制,也有他的规则,其中一个规则就是基线。如图:



android Paint 文字 行间距 paintnet修改文字_Paint_06


会发现,基线的位置(红色),和我们刚才绘制的红点坐标(50,50)位置,是一致。
是的。一般而言,(x,y)所代表的位置是所画图形对应的矩形的左上角点。但在drawText()中是非常例外的,y所代表的是基线的位置!

所以,这里知道文本坐标的意义,那对于第一个问题,就很好解决了,只要把y的坐标改变一下就可以了。(后面学习FontMetrics,也可以让字体准确在屏幕左上角显示)

对于第2个问题,也是改变y的坐标,但需要居中显示,这个y值应该是确定的才对。现在就需要看看绘制文本其他规则:

这里借鉴引入一张图片:

android Paint 文字 行间距 paintnet修改文字_Paint_07

  • baseLine:绘制文本的开始的基线
  • ascent: 系统建议绘制单个字符时,字符应当的最高高度所在线,特殊字符除外
  • descent:系统建议绘制单个字符时,字符应当的最低高度所在线,特殊字符除外
  • top: 可绘制的最高高度所在线,对于特殊字符会超出ascent高度
  • bottom:可绘制的最低高度所在线,对于特殊字符会超出descent高度

baseline是开始的基线,把它作为参照线,那么根据android的xy轴方向,baseline上方的值为负,下方的值为正。那这些线是怎么计算出来的呢?Android提供了FontMetrics。

FontMetrics

FontMetrics是字体测量类,根据字体、字号测量以上建议值。它包含了这些测量参数:

FontMetrics.ascent:float 类型
FontMetrics.descent:float 类型
FontMetrics.top:float 类型
FontMetrics.bottom:float 类型
FontMetrics.leading:float 类型

这里稍微提下:leading指的是相邻两行的额外间隔,即上行的bottom线,与下行的top之间的空隙。如果只有一行,那么该值就是0;

获取FontMetrics对象:

Paint.FontMetrics fontMetrics = mTestPaint.getFontMetrics();//得到测量参数是float类型
Paint.FontMetrics fontMetrics = mTestPaint.getFontMetricsInt();//得到测量参数是int类型
getFontMetrics(FontMetrics fontMetrics) //计算结果会直接填进传入的 FontMetrics 对象,而不是重新创建一个对象。这种用法在需要频繁获取 FontMetrics 的时候性能会好些。

有了这个测量类,那我们就可以解决第2个问题:

首先分析:

在开始的时候,我们传入的坐标是屏幕的中点:

canvas.drawText("在屏幕中央的文本", getWidth()/2, getHeight()/2, mTestPaint);

通过问题1,我们知道y的坐标值(getHeight()/2)是基线的位置,那肯定是不会居中显示了,需要改变y的坐标。通过原先的图片可以看出,字体需要往下移,才能居中,如示意图。那么移多少呢?



android Paint 文字 行间距 paintnet修改文字_特殊字符_08


在示意图中,左边是原先图片,右边是改变后居中的图片。这里注意左右2边,文本框中的红线,该线是绘制文本区域的中心线,即((FontMetrics.bottom - FontMetrics.top)/2)。从图中可以看出,只要将文本的中心线显示在屏幕的中心(即如果y值是中心线的值,那么就居中)那么字体就是居中的。怎么计算?借鉴一张图:



android Paint 文字 行间距 paintnet修改文字_自定义view _09


B = baseLine + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom,其中baseLine = getHeight()/2

最终:

canvas.drawText("在屏幕中央的文本", getWidth()/2, getHeight()/2 + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom, mTestPaint);

上面也说过,只有特殊字符,才会显示在top、bottom位置,平时我们只考虑ascent、descent的位置,所以也可以是:

canvas.drawText("在屏幕中央的文本", getWidth()/2, getHeight()/2 + (fontMetrics1.descent) + (fontMetrics1.ascent)/2 - fontMetrics1.bottom, mTestPaint);

最终看解决的效果图:



android Paint 文字 行间距 paintnet修改文字_特殊字符_10


嘎嘎,已经居中了。所以,很多时候,一直在调试文字居中问题,怎么调多不对,那是脑子多没有这种概念…

但是,说了一大堆,还有那么多参数要理解,相信很多人还是很拒绝使用这种方式。那就在提供一种更简单的,通过Paint的getTextBound()方法。

void getTextBounds(String text, int start, int end, Rect bounds)
获取文字显示范围

参数:
text:需要测量的字符串
start:字符串开始的索引
end:字符串结束的索引
bounds:返回的测量结果,即仅包裹文本的最小区域

具体是哪个位置呢?如图红色显示区域:



android Paint 文字 行间距 paintnet修改文字_特殊字符_11


那么,有了这样的一个区域,我们就不用管前面那些复杂的参数了。(一般网上的处理方式也是这种),具体实现如下:

/**
* 对于第2个问题的另一种解决
*/
mTestPaint.setTextSize(60);
String text = "在屏幕中央的文本";
Rect textRect = new Rect();
mTestPaint.getTextBounds(text, 0, text.length(), textRect);
canvas.drawText(text, getWidth()/2 - textRect.width()/2, getHeight()/2 + textRect.height()/2, mTestPaint);


/**
* 对于第2个问题的另一种解决
*/
mTestPaint.setTextSize(60);
String text = "在屏幕中央的文本";
Rect textRect = new Rect();
mTestPaint.getTextBounds(text, 0, text.length(), textRect);
canvas.drawText(text, getWidth()/2 - textRect.width()/2, getHeight()/2 + textRect.height()/2, mTestPaint);

对于文本的测量,还有很多方法:

float measureText(String text) 获取文本 占用 的宽度(包括文本间隙)

getTextWidths(String text, float[] widths) 获取每个字符的宽度,将值保存在widths

int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)

参数:
text:测量为文本
measureForwards:测量文字的方向
maxWidth:测量区域最大的宽度,超过该区域的文本,将不会测量
measuredWidth:保存测量的宽度值

float getFontSpacing() 获取推荐的行距,即相邻2行baseline的距离

结束

好了,关于文本的介绍就到这里了。api方法都比较简单,这里主要是要了解,文本显示位置与基线的关系,后面如果遇到文本不居中的问题,能想到这种方式处理即可。