问题:
自定义view的时候,drawText()方法画出的文字竖直方向没有居中,如下代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//y轴
canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), paint);
//x轴
canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, paint);
canvas.drawText("helloWorld", getWidth() / 2, getHeight() / 2, paint);
}
在自定义view中画了x轴和y轴方便看清位置。文字“helloWorld”的位置为(getWidth() / 2, getHeight() / 2),然而得出的效果是这样的:
可见文字并没有位于控件的正中间,而是向上偏移了一点;
分析:
查看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(@NonNull String text, float x, float y, @NonNull Paint paint) {
super.drawText(text, x, y, paint);
}
从上面的代码看出参数y代表的是文字的“baseline”位置,所以说android中drawText是以文字的基线为标准的,我们要文字“居中”的话就必须把偏移量计算出来。这就需要用到一个非常关键的类: Paint.FontMetrics
其中主要参数代表含义如下:
-
基准点是baseline,值为0
-
Ascent是baseline之上至字符最高处的距离,值为负数
-
Descent是baseline之下至字符最低处的距离,值为正数
-
Leading文档说的很含糊,其实是上一行字符的descent到下一行的ascent之间的距离
-
Top指的是指的是最高字符到baseline的值,即ascent的最大值,为负数
-
同上,bottom指的是最下字符到baseline的值,即descent的最大值,为正数;
那么文字中间的值为 ( top+bottom) / 2,因为以baseline为基线(0),所以偏移量:
offset = 0 - ( top+bottom) / 2;
讲上述代码改为:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//y轴
canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), paint);
//x轴
canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, paint);
String text="aaaa";
fontMetrics = paint.getFontMetrics();
float top=fontMetrics.top;
float bottom=fontMetrics.bottom;
float offset=(top+bottom)/2;
canvas.drawText(text, getWidth() / 2, getHeight() / 2-offset, paint);
}
效果如下:
文字高度居中了,但是又发现新的问题:上述算法是计算的是字符占据的最大高度,如“h”和“e”两者的top值是一样的,如果全部字符都比较“矮”会出现什么情况呢?
将“helloWorld”改为“aaaaa”:
其实这样没有错,因为“a”、“s”、“u”、“r”、“w”这些字符本身比较矮,上部分留白了所以看起来没有居中。但是如果想让这些“矮子”也居中怎么办呢? android中提供了 getTextBound()方法:
/**
* Retrieve the text boundary box and store to bounds.
*
* Return in bounds (allocated by the caller) the smallest rectangle that
* encloses all of the characters, with an implied origin at (0,0).
*
* @param text string to measure and return its bounds
* @param start index of the first char in the string to measure
* @param end 1 past the last char in the string to measure
* @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
*/
public void getTextBounds(String text, int start, int end, Rect bounds) {
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
if (bounds == null) {
throw new NullPointerException("need bounds Rect");
}
nGetStringBounds(mNativePaint, text, start, end, mBidiFlags, bounds);
}
意思返回绘制文字边界的最小矩形,也就是说如果文字全是a、u等矮字符那么它返回的高度就是“a”字符的高度,不会像上述 fontMetrics.top 方法加上留白的距离;
修改代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//y轴
canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), paint);
//x轴
canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, paint);
String text="aaaaa";
rect = new Rect();
paint.getTextBounds(text,0,text.length(),rect);
float offset=(rect.top+rect.bottom)/2;
canvas.drawText(text, getWidth() / 2, getHeight() / 2-offset, paint);
}
效果:
总结:
1 . 常用方法:
float offset=(paint.getFontMetrics().top+paint.getFontMetrics().bottom)/2;
canvas.drawText(text, x, y-offset, paint);
此方法如果全部文字都比较矮如 “aaaa” ,文字上方会留白。
2 . 让矮文字也居中的方法:
rect = new Rect();
paint.getTextBounds(text,0,text.length(),rect);
float offset=(rect.top+rect.bottom)/2;
canvas.drawText(text,x, y-offset, paint);
如果是频繁变动的文字,当文字变成都是矮文字时会因为文字上移出现视觉上的跳动(看需求选择吧=-=)。