本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
1.写在前面
在实现自定义控件的过程中,常常会有绘制居中文字的需求,于是在网上搜了一些相关的博客,总是看的一脸懵逼,就想着自己分析一下,在此记录下来,希望对大家能够有所帮助。
2.绘制一段文本
首先把坐标原点移动到控件中心(默认坐标原点在屏幕左上角),这样看起来比较直观一些,然后绘制x、y轴,此时原点向上y为负,向下y为正,向左x为负,向右x为正,以(0,0)坐标开始绘制一段文本:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// 将坐标原点移到控件中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// x轴
canvas.drawLine(-getWidth() / 2, 0, getWidth() / 2, 0, paint);
// y轴
canvas.drawLine(0, -getHeight() / 2, 0, getHeight() / 2, paint);
// 绘制文字
paint.setTextSize(sp2px(50));
canvas.drawText("YangLe", 0, 0, paint);
}
看下绘制的文本:
咦,为什么绘制的文本在第一象限,y坐标不是指定的0吗,为什么文本没有在x轴的上面或下面,而是穿过了x轴,带着这些疑问继续往下看:
首先看一个重要的类:
public static class FontMetrics {
/**
* The maximum distance above the baseline for the tallest glyph in
* the font at a given text size.
*/
public float top;
/**
* The recommended distance above the baseline for singled spaced text.
*/
public float ascent;
/**
* The recommended distance below the baseline for singled spaced text.
*/
public float descent;
/**
* The maximum distance below the baseline for the lowest glyph in
* the font at a given text size.
*/
public float bottom;
/**
* The recommended additional space to add between lines of text.
*/
public float leading;
}
FontMetrics类是Paint的一个内部类,主要定义了绘制文本时的一些关键坐标位置,看下这些值都代表什么:
看图说话:
top:从基线(x轴)向上绘制区域的最高点,此值为负值
ascent:单行文本,从基线(x轴)向上绘制的推荐最高点,此值为负值
baseline:基线,此值为0
descent:单行文本,从基线(x轴)向下绘制的推荐最低点,此值为正值
bottom:从基线(x轴)向下绘制区域的最低点,此值为正值
leading:推荐的额外行距,一般为0
下面再来看看drawText这个方法:
/**
* Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
* based on the Align setting in the paint.
*
* @param text The text to be drawn
* @param x The x-coordinate of the origin of the text being drawn
* @param y The y-coordinate of the baseline of the text being drawn
* @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawText( String text, float x, float y, Paint paint) {
super.drawText(text, x, y, paint);
}
重点看下x、y参数的含义:
x:绘制文本的起始x坐标
y:绘制文本的baseline在y轴方向的位置
有点难理解,举个栗子,上文中的x、y参数传的是(0,0),此时的baseline正好是坐标系中x轴,就相当于从y轴开始向右绘制,以x轴作为文本的baseline进行绘制。
如果参数传(0,10),此时绘制文本的baseline从x轴开始向下移动10px,也就是以y10作为文本的baseline进行绘制,y10就是绘制文本的baseline在y轴方向的位置。
注意:baseline是绘制文本的基线,相对于绘制文本区域来说,相当于x轴,向上为负(top、ascent),向下为正(descent、bottom),但是这个x轴并不是控件的x轴,切记切记!!!
还记得我们在上文中提出的疑问吗,这下可以解释了:
-
为什么绘制的文本在第一象限?
因为我们把坐标原点移到了控件中心,文本的baseline正好为x轴,top、ascent值为负,所以绘制的文本在第一象限。
-
y坐标不是指定的0吗,为什么文本没有在x轴的上面或下面,而是穿过了x轴?
drawText方法默认x轴方向是从左到右绘制的,y轴方向是从baseline为基准绘制的,文中的baseline正好为x轴,以baseline为基准绘制文本向下还有一段距离,所以文本穿过了x轴。
3.绘制居中的文本
在上文中,我们学习了如何绘制一段文本,以及其中参数和坐标的含义,接下来进入正题,看下如何才能绘制居中的文本。
首先看一张图,此时文本的baseline正好为x轴,如果想要文本居中显示的话,就需要先计算文本的宽度和高度:
宽度:调用Paint的measureText方法就可以获得文本的宽度
高度:文本的高度就是实际绘制区域的高度,可以用(fontMetrics.descent - fontMetrics.ascent)获取,因为ascent为负数,所以最终算出来的是两者的和
现在有了宽度,把绘制文本的x坐标向左移动(宽度 / 2)就可以水平居中,但是垂直方向就不能这么干了,我们要将文本向下移动baseline到文本中心的距离,也就是(高度 / 2 - fontMetrics.descent),如下图所示:
现在的公式为:
float baseLineY
= (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
= -fontMetrics.ascent / 2 - fontMetrics.descent / 2;
= -(fontMetrics.ascent + fontMetrics.descent) / 2;
= Math.abs(fontMetrics.ascent + fontMetrics.descent) / 2;
Paint中也有获取ascent和descent值的方法,所以公式最终为:
float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2;
注意:此公式是相对于坐标原点在控件中心来计算的,如果坐标原点在左上角,baseLineY需要加上控件高度的一半。
float baseLineY = height / 2 + Math.abs(paint.ascent() + paint.descent()) / 2;
看下代码:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// 将坐标原点移到控件中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// x轴
canvas.drawLine(-getWidth() / 2, 0, getWidth() / 2, 0, paint);
// y轴
canvas.drawLine(0, -getHeight() / 2, 0, getHeight() / 2, paint);
// 绘制居中文字
paint.setTextSize(sp2px(50));
paint.setColor(Color.GRAY);
// 文字宽
float textWidth = paint.measureText("YangLe'Blog");
// 文字baseline在y轴方向的位置
float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2;
canvas.drawText("YangLe'Blog", -textWidth / 2, baseLineY, paint);
}
看下居中了吗:
大功告成!
4.文本居中的公式
坐标原点在控件中心:
float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2;
坐标原点在控件左上角:
float baseLineY = height / 2 + Math.abs(paint.ascent() + paint.descent()) / 2;
5.写在最后
源码已经上传到GitHub上了,欢迎Fork,觉得还不错就Start一下吧!