在自定义view中用到canvas绘制文字的时候常常会碰到要求文字居中或者自动换行的需求,接下来我就介绍一下我的实现方法。
首先绘制文字居中:
绘制文字时可以通过Paint的getFontMetrics()方法得到文字度量相关信息如下图:
上图所示的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);
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);
|
看一下显示效果:
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();
|
看一下效果:
自动换行实现了,怎么实现文字在显示区域内居中呢?需要计算文字的高度然后设置绘制文字的起始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
|
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(); }
|
最后的效果