0%

Android自定义View使用canvas绘制文字实现居中、自动换行

在自定义view中用到canvas绘制文字的时候常常会碰到要求文字居中或者自动换行的需求,接下来我就介绍一下我的实现方法。
首先绘制文字居中:
绘制文字时可以通过Paint的getFontMetrics()方法得到文字度量相关信息如下图:
20130827162047703
上图所示的top、bottom、ascent、descent都是根据Paint的getFontMetrics()得到的FontMetrics取到的。
baseline是绘制文字Y坐标的基准点,也就是Y坐标的0点位置,向上为负,向下为正。所以descent是一个正值而ascent是一个负值。所以文字的真实高度是descent-ascent。

看一下绘制单行文字居中代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Paint textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(30);
/**
* 设置绘制文字时起始点X坐标的位置
* CENTER:以文字的宽度的中心点为起始点向两边绘制
* LEFT:以文字左边为起始点向右边开始绘制
* RIGHT:以文字宽度的右边为起始点向左边绘制
*/
textPaint.setTextAlign(Paint.Align.CENTER);

//获取文字度量信息
Paint.FontMetrics fm = textPaint.getFontMetrics();
float textHeight = fm.descent-fm.ascent;

//绘制文字的矩形框范围
RectF rectF = new RectF(100,100,500,100+textHeight);
Paint paint = new Paint();
paint.setColor(Color.YELLOW);

canvas.drawRect(rectF, paint);
canvas.drawText("Hello gafbcj World!", rectF.left + rectF.width() / 2, rectF.bottom-fm.descent, textPaint);

看一下显示效果:
20150813151834
OK,文字居中的效果搞定了.下面看看自动换行的实现。
用canvas直接drawText是不能自动换行的,需要用到StaticLayout来实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TextPaint textPaint = new TextPaint();
textPaint.setColor(Color.BLUE);
textPaint.setTextSize(30);

Rect rect = new Rect(100,100,300,400);

String text = "这是一串需要进行换行显示的文字。";

//文字自动换行
StaticLayout layout = new StaticLayout(text, textPaint, rect.width(), Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F,true);
canvas.save();
textPaint.setTextAlign(Paint.Align.LEFT);
//文字的位置
canvas.translate(rect.left , rect.top);
layout.draw(canvas);
canvas.restore();

看一下效果:
20150813154153
自动换行实现了,怎么实现文字在显示区域内居中呢?需要计算文字的高度然后设置绘制文字的起始Y坐标.这就需要对文字进行处理,计算出文字显示的行数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 将文字拆分成每一行放到Vector里
*/
public Vector<String> getTextLinesVector(TextPaint paint, String content, float maxHeight,
float maxWidth) {
Vector<String> mString = new Vector<>();
int mRealLine = 0;// 字符串真实的行数
char ch;
int w = 0;
int istart = 0;
float mFontHeight = getFontHeight(paint);
int mMaxLinesNum = (int)(maxHeight / mFontHeight);//显示的最大行数
int count = content.length();
for (int i = 0; i < count; i++) {
ch = content.charAt(i);
float[] widths = new float[1];
String str = String.valueOf(ch);
paint.getTextWidths(str, widths);
if (ch == '\n') {
mRealLine++;// 真实的行数加一
mString.addElement(content.substring(istart, i));
istart = i + 1;
w = 0;
} else {
w += (int) Math.ceil(widths[0]);
if (w > maxWidth) {
mRealLine++;// 真实的行数加一
mString.addElement(content.substring(istart, i));
istart = i;
i--;
w = 0;
} else {
if (i == count - 1) {
mRealLine++;// 真实的行数加一
mString.addElement(content.substring(istart, count));
}
}
}
//当真实行数大于显示的最大行数时跳出循环
if(mRealLine == mMaxLinesNum){
break;
}
}

return mString;
}

/**
*得到文字的高度
*/
private float getFontHeight(TextPaint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();// 得到系统默认字体属性
return fm.bottom - fm.top;
}

大家可能发现这里计算文字高度用的是bottom-top而不是上面用的descent-ascent,这是因为descent-ascent是文字的真实高度,而bottom-top是文字显示区域的高度,绘制的时候文字上下是有一定的间距的.
获得拆分每一行以后的文字再用上面的换行的方法进行绘制,调整绘制时的起始位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void drawLinesText(Canvas canvas) {
TextPaint textPaint = new TextPaint();
textPaint.setColor(Color.BLUE);
textPaint.setTextSize(30);

Rect rect = new Rect(100,100,300,400);

String text = "这是一串需要进行换行显示的文字。";

//获得自动换行后的文字
Vector<String> vector = getTextLinesVector(textPaint, text, rect.height(), rect.width());
text = vectorToString(vector);
//文字自动换行
StaticLayout layout = new StaticLayout(text,textPaint, rect.width(), Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F,true);
canvas.save();
textPaint.setTextAlign(Paint.Align.CENTER);
//文字的位置
canvas.translate(rect.left + rect.width()/2, rect.top+ (rect.height() - getFontHeight(textPaint)*vector.size())/2);
layout.draw(canvas);
canvas.restore();
}

private String vectorToString(Vector<String> strs) {
StringBuffer ss = new StringBuffer();
for (String s : strs) {
ss.append(s+"\n");
}
return ss.toString();
}

最后的效果
20150813160158

 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!