Java 如何垂直对齐文本?

Java 如何垂直对齐文本?,java,android,canvas,drawtext,Java,Android,Canvas,Drawtext,目标:纯画布上的Android>=1.6 假设我想写一个函数,画一个(宽,高)大的红色矩形,然后在里面画一个黑色的Hello World文本。我希望文本在视觉上位于矩形的中心。让我们试试: void drawHelloRectangle(Canvas c, int topLeftX, int topLeftY, int width, int height) { Paint mPaint = new Paint(); // height of 'Hello Wor

目标:纯画布上的Android>=1.6

假设我想写一个函数,画一个(宽,高)大的红色矩形,然后在里面画一个黑色的Hello World文本。我希望文本在视觉上位于矩形的中心。让我们试试:

void drawHelloRectangle(Canvas c, int topLeftX, 
        int topLeftY, int width, int height) {
    Paint mPaint = new Paint();
    // height of 'Hello World'; height*0.7 looks good
    int fontHeight = (int)(height*0.7);

    mPaint.setColor(COLOR_RED);
    mPaint.setStyle(Style.FILL);
    c.drawRect( topLeftX, topLeftY, topLeftX+width, topLeftY+height, mPaint);

    mPaint.setTextSize(fontHeight);
    mPaint.setColor(COLOR_BLACK);
    mPaint.setTextAlign(Align.CENTER);
    c.drawText( "Hello World", topLeftX+width/2, ????, mPaint);
}
现在我不知道在drawText的参数中放置什么,该参数由
标记,也就是说,我不知道如何垂直对齐文本

差不多

??=左上角+高度/2+ fontHeight/2-fontHeight/8


看起来或多或少还行,但一定有更好的方法。

使用mPaint.getTextBounds()可以询问绘制文本时文本的大小,然后使用该信息可以计算要绘制文本的位置。

根据steelbytes的响应,更新后的代码看起来类似于:

void drawHelloRectangle(Canvas c, int topLeftX, int topLeftY, int width, int height) {
    Paint mPaint = new Paint();
    // height of 'Hello World'; height*0.7 looks good
    int fontHeight = (int)(height*0.7);

    mPaint.setColor(COLOR_RED);
    mPaint.setStyle(Style.FILL);
    c.drawRect( topLeftX, topLeftY, topLeftX+width, topLeftY+height, mPaint);

    mPaint.setTextSize(fontHeight);
    mPaint.setColor(COLOR_BLACK);
    mPaint.setTextAlign(Align.CENTER);
    String textToDraw = new String("Hello World");
    Rect bounds = new Rect();
    mPaint.getTextBounds(textToDraw, 0, textToDraw.length(), bounds);
    c.drawText(textToDraw, topLeftX+width/2, topLeftY+height/2+(bounds.bottom-bounds.top)/2, mPaint);
}
从“基线”到“中心”的距离应为
-(mPaint.descent()+mPaint.Ascend())/2

public static PointF getTextCenterToDraw(String text, RectF region, Paint paint) {
    Rect textBounds = new Rect();
    paint.getTextBounds(text, 0, text.length(), textBounds);
    float x = region.centerX() - textBounds.width() * 0.4f;
    float y = region.centerY() + textBounds.height() * 0.4f;
    return new PointF(x, y);
}
用法:

PointF p = getTextCenterToDraw(text, rect, paint);
canvas.drawText(text, p.x, p.y, paint);

由于在Y处绘制文本意味着文本的基线将从原点向下延伸Y个像素,因此当您希望在
(宽度、高度)
尺寸的矩形内居中文本时,需要做的是:

paint.setTextAlign(Paint.Align.CENTER);  // centers horizontally
canvas.drawText(text, width / 2, (height - paint.ascent()) / 2, paint);
请记住,上升是负的(这解释了负号)

这不考虑下降,这通常是您想要的(上升通常是高于基线的上限高度)。

cx
cy
为中心的示例: 为什么
height()/2f
的工作原理不一样?
exactCentre()
=
(顶部+底部)/2f

height()/2f
=
(底部-顶部)/2f


只有当
top
0
时,才会产生相同的结果。这可能适用于所有大小的某些字体,或某些大小的其他字体,但并非适用于所有大小的所有字体。

我在试图解决我的问题时偶然发现了这个问题,@Weston的答案对我来说很好

对于Kotlin:

这里有一个SkiaSharp C#扩展方法,供任何人使用


每次你把安卓系统放在问题标题中,一只小猫就会死,所以请停止这样做。谢谢。9年后,这个问题和公认的答案仍然相关GetTextBounds()属于绘画,而不是画布记住,当你说垂直居中的文本时,你可能指的是字符的升序部分,而不是基线。当您使用Align.CENTER绘制时,Y坐标将以字符基线为中心。@CameronLowellPalmer您能详细说明一下吗?@Peterdk我建议您自己尝试一下,但您测量的是绘制文本的边界。这意味着,如果文本下降到基线以下,并且您正在排列多个单独绘制的单词,您将看到文本没有按照预期的方式排列。单词“grow”下降到基线以下,单词“hi”则没有。因此,如果你找到这两个单词的文本边界的中心,然后除以2来计算高度,你会得到一个不理想的结果。这应该是可以接受的答案。它不仅是其中最简单的一个,而且是唯一一个真正的、光学上垂直居中的文本,适当地考虑了上升和下降(不是一般的,而是要显示的实际文本中的实际文本)。通过许多环和圈来破解这个-然后它就这么简单。这就是你想要的答案!有人能告诉我为什么textBounds.height()/2不能按预期工作吗?(至少对我来说不是。)我找到了这个答案,用centerY()替换了height(),现在它可以工作了。首先,代码没有考虑文本的对齐方式(例如,如果“对齐”为“中心”,则它将不起作用)2。由于这种方法在我们实际绘制文本时只是平移文本边界的中心,因此它将不符合边界(不考虑文本的上升和下降)@Alex它会这样做,如果它不符合您的要求,我很抱歉。“当我们实际绘制文本时,它将不符合边界”它符合边界,我们只是移动了中心。这对我不起作用,但公认的答案非常有效。如果字体较小,这种方法的效果可能会更好。我在这个问题中尝试了一些技巧,发现你的整体效果最好。与公认的答案不同,无论你的文本是什么,这个答案都能提供一致的结果。与公认的答案不同的是,你的答案以大写字母的中点为中心,我认为这是大多数人所期望的。我刚刚尝试了这个答案,但发现文本并不像你建议的那样以大写字母为中心。取而代之的是,我还必须使用它来获得这种行为。请不要只发布代码作为答案,还要提供一个解释,说明您的代码是做什么的,以及它是如何解决问题的。带有解释的答案通常更有帮助,质量更好,更容易吸引选票。
paint.setTextAlign(Paint.Align.CENTER);  // centers horizontally
canvas.drawText(text, width / 2, (height - paint.ascent()) / 2, paint);
private final Rect textBounds = new Rect(); //don't new this up in a draw method

public void drawTextCentred(Canvas canvas, Paint paint, String text, float cx, float cy){
  paint.getTextBounds(text, 0, text.length(), textBounds);
  canvas.drawText(text, cx - textBounds.exactCenterX(), cy - textBounds.exactCenterY(), paint);
}
private fun drawText(canvas: Canvas) {
    paint.textSize = 80f
    val text = "Hello!"
    val textBounds = Rect()
    paint.getTextBounds(text, 0, text.length, textBounds);
    canvas.drawText(text, cx- textBounds.exactCenterX(), cy - textBounds.exactCenterY(), paint);
    //in case of another Rect as a container:
    //canvas.drawText(text, containerRect.exactCenterX()- textBounds.exactCenterX(), containerRect.exactCenterY() - textBounds.exactCenterY(), paint);
}
public static void DrawTextCenteredVertically(this SKCanvas canvas, string text, SKPaint paint, SKPoint point)
{
    var textY = point.Y + (((-paint.FontMetrics.Ascent + paint.FontMetrics.Descent) / 2) - paint.FontMetrics.Descent);
    canvas.DrawText(text, point.X, textY, paint);
}
private final Rect textBounds = new Rect();//No need o create again

public void drawTextCentred(Canvas canvas, Paint paint, String text, float cx, float cy, float desiredWidth) {
    final float testTextSize = 48f;
    paint.setTextSize(testTextSize);
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, text.length(), bounds);
    float desiredTextSize = testTextSize * desiredWidth / bounds.width();
    paint.setTextSize(desiredTextSize);
    paint.getTextBounds(text, 0, text.length(), textBounds);
    paint.setTextAlign(Paint.Align.CENTER);
    canvas.drawText(text, cx, cy - textBounds.exactCenterY(), paint);
}