Android Canvas.drawText垂直居中问题三种情况

本文详细介绍了在Android中使用Canvas.drawText实现文本垂直居中的三种方法:以baseline为基准、以目标矩形为参照物和以某个点为基准居中。通过FontMetrics对象和精确计算,确保文字在不同场景下都能准确居中显示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

官方文档:
public void drawText (String text, float x, float y, Paint paint)

Parameters
text The text to be drawn
x The x-coordinate of the origin of the text being drawn
y The y-coordinate of the origin of the text being drawn
paint The paint used for the text (e.g. color, size, style)

**

方案一:以baseline为基准

**

Canvas 作为绘制文本时,使用FontMetrics对象,计算位置的坐标。 它的思路和java.awt.FontMetrics的基本相同。
FontMetrics对象它以四个基本坐标为基准,分别为:

FontMetrics.top
FontMetrics.ascent
FontMetrics.descent
FontMetrics.bottom

这里写图片描述

   Paint textPaint = new Paint( Paint.ANTI_ALIAS_FLAG);
   textPaint.setTextSize( 35);
   textPaint.setColor( Color.WHITE);

   // FontMetrics对象
   FontMetrics fontMetrics = textPaint.getFontMetrics();
   String text = "abcdefghijklmnopqrstu";

   // 计算每一个坐标
   float baseX = 0;
   float baseY = 100;
   float topY = baseY + fontMetrics.top;
   float ascentY = baseY + fontMetrics.ascent;
   float descentY = baseY + fontMetrics.descent;
   float bottomY = baseY + fontMetrics.bottom;

   // 绘制文本
   canvas.drawText( text, baseX, baseY, textPaint);

   // BaseLine描画
   Paint baseLinePaint = new Paint( Paint.ANTI_ALIAS_FLAG);>
   baseLinePaint.setColor( Color.RED);
   canvas.drawLine(0, baseY, getWidth(), baseY, baseLinePaint);

   // Base描画
   canvas.drawCircle( baseX, baseY, 5, baseLinePaint);

   // TopLine描画
   Paint topLinePaint = new Paint( Paint.ANTI_ALIAS_FLAG);
   topLinePaint.setColor( Color.LTGRAY);
   canvas.drawLine(0, topY, getWidth(), topY, topLinePaint);

   // AscentLine描画
   Paint ascentLinePaint = new Paint( Paint.ANTI_ALIAS_FLAG);
   ascentLinePaint.setColor( Color.GREEN);
   canvas.drawLine(0, ascentY, getWidth(), ascentY, ascentLinePaint);

   // DescentLine描画
   Paint descentLinePaint = new Paint( Paint.ANTI_ALIAS_FLAG);
   descentLinePaint.setColor( Color.YELLOW);
   canvas.drawLine(0, descentY, getWidth(), descentY, descentLinePaint);

   // ButtomLine描画
   Paint bottomLinePaint = new Paint( Paint.ANTI_ALIAS_FLAG);
   bottomLinePaint.setColor( Color.MAGENTA);
   canvas.drawLine(0, bottomY, getWidth(), bottomY, bottomLinePaint);

drawText画字符串是baseline对齐的。所以要特别注意这点,不然画文字可能画到其它地方而误以为没有画出来。

如果baseline对齐的话:底端的Y坐标是:(行高-字体高度)/2+字体高度 ,但是字符串并不居中,经过测试如果:(行高-字体高度)/2+字体高度-6 ,就稍微居中了一点。 以上的方法只是一个取巧的做法,网上也没有找到设置文字居中的方法。

按上面办法会有误差。加上那段距离应该就行了:

FontMetrics fontMetrics = mPaint.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
float newY = baseY + offY;
canvas.drawText(text, baseX, newY, paint);

**

方案二:以目标矩形为参照物

**
Canvas绘图,drawText里的origin是以baseline为基准的,直接以目标矩形的bottom传进drawText,字符位置会偏下。这样写代码:

@Override
public void onDraw (Canvas canvas) {
    Rect targetRect = new Rect(50, 50, 1000, 200);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStrokeWidth(3);
    paint.setTextSize(80);
    String testString = "测试:ijkJQKA:1234";
    paint.setColor(Color.CYAN);
    canvas.drawRect(targetRect, paint);
    paint.setColor(Color.RED);
    canvas.drawText(testString, targetRect.left, targetRect.bottom, paint);
}

运行结果

首先自己动手做实验,自己定一个baseline,然后把文字画上去,再画上FontMetrics的几条线。FontMetrics里是字体图样的信息,有float型和int型的版本,都可以从Paint中获取。它的每个成员数值都是以baseline为基准计算的,所以负值表示在baseline之上。实验代码:

@Override
public void onDraw (Canvas canvas) {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStrokeWidth(3);
    paint.setTextSize(80);
    FontMetricsInt fmi = paint.getFontMetricsInt();
    String testString = "测试:ijkJQKA:1234";
    Rect bounds1 = new Rect();
    paint.getTextBounds("测", 0, 1, bounds1);
    Rect bounds2 = new Rect();
    paint.getTextBounds("测试:ijk", 0, 6, bounds2);
    // 随意设一个位置作为baseline
    int x = 200;
    int y = 400;
    // 把testString画在baseline上
    canvas.drawText(testString, x, y, paint);
    // bounds1
    paint.setStyle(Style.STROKE);  // 画空心矩形
    canvas.save();
    canvas.translate(x, y);  // 注意这里有translate。getTextBounds得到的矩形也是以baseline为基准的
    paint.setColor(Color.GREEN);        
    canvas.drawRect(bounds1, paint);
    canvas.restore();
    // bounds2
    canvas.save();
    paint.setColor(Color.MAGENTA);
    canvas.translate(x, y);
    canvas.drawRect(bounds2, paint);
    canvas.restore();
    // baseline
    paint.setColor(Color.RED);
    canvas.drawLine(x, y, 1024, y, paint);
    // ascent
    paint.setColor(Color.YELLOW);
    canvas.drawLine(x, y+fmi.ascent, 1024, y+fmi.ascent, paint);
    // descent
    paint.setColor(Color.BLUE);
    canvas.drawLine(x, y+fmi.descent, 1024, y+fmi.descent, paint);
    // top
    paint.setColor(Color.DKGRAY);
    canvas.drawLine(x, y+fmi.top, 1024, y+fmi.top, paint);
    // bottom
    paint.setColor(Color.GREEN);
    canvas.drawLine(x, y+fmi.bottom, 1024, y+fmi.bottom, paint);
}

运行结果
红线是baseline,最上面的灰线是FontMetrics.top,最下面的绿线是FontMetrics.bottom。(绿色的bottom和蓝色的descent非常接近)

从图中可知,字符本身是在灰线和绿线之间居中的,知道这个就好办了。网上说的使用paint.getTextBounds的方法都不靠谱,可以看到对一个“测”字和6个字得到的bounds是不同的,图中的矩形能很好地表示这个函数得到的是字符的边界,而不是字体的边界。

FontMetrics.top的数值是个负数,其绝对值就是字体绘制边界到baseline的距离。
所以如果是把文字画在 FontMetrics高度的矩形中, drawText就应该传入 -FontMetrics.top。
要画在targetRect的居中位置,baseline的计算公式就是:
targetRect.centerY() - (FontMetrics.bottom - FontMetrics.top) / 2 - FontMetrics.top
优化后即:

(targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2

所以最开始的代码应该改成(顺便加入水平居中):

@Override
public void onDraw (Canvas canvas) {
    Rect targetRect = new Rect(50, 50, 1000, 200);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStrokeWidth(3);
    paint.setTextSize(80);
    String testString = "测试:ijkJQKA:1234";
    paint.setColor(Color.CYAN);
    canvas.drawRect(targetRect, paint);
    paint.setColor(Color.RED);
    FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        // 转载请注明出处:http://blog.csdn.net/hursing
    int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
    // 下面这行是实现水平居中,drawText对应改为传入targetRect.centerX()
    paint.setTextAlign(Paint.Align.CENTER);
    canvas.drawText(testString, targetRect.centerX(), baseline, paint);
}

运行结果

**

方案三:以某个点为基准居中

**

float offset  = Math.abs(textPaint.descent() + textPaint.ascent()) / 2

canvas.drawText(str, x, y + offset, paint);

前两种解决方案来自下面两篇博客
http://blog.youkuaiyun.com/hursing
http://blog.youkuaiyun.com/lvxiangan/article/details/8540774

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值