【问题标题】:How to align text vertically?如何垂直对齐文本?
【发布时间】:2011-06-22 00:23:00
【问题描述】:

目标:纯 Canvas 上的 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 参数中放什么,即我不知道如何垂直对齐文本。

类似

???? = topLeftY + 高度/2 + fontHeight/2 - fontHeight/8;

看起来或多或少都可以,但一定有更好的方法。

【问题讨论】:

  • 每次您在问题标题中输入 Android 都会有一只小猫死去,所以请停止这样做。谢谢。
  • 9 年后,这个问题和接受的答案仍然相关

标签: java android canvas drawtext


【解决方案1】:

cxcy 为中心的示例:

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);
}

为什么height()/2f 不一样?

exactCentre() = (top + bottom) / 2f.

height()/2f = (bottom - top) / 2f

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

【讨论】:

  • 这应该是公认的答案。它不仅是其中最简单的一种,而且是唯一一种真正将文本垂直居中并适当考虑上升和下降的一种(不是一般情况,而是要显示的实际文本中存在的实际文本)。
  • 经历了很多麻烦和循环来破解这个 - 然后就这么简单。这是您正在寻找的答案!
  • 有人能告诉我为什么 textBounds.height() / 2 不能按预期工作吗? (至少不适合我。)我找到了这个答案并用 centerY() 替换了 height() ,现在它可以工作了......
  • 1.首先,代码没有考虑文本的对齐方式(例如,如果 align 为 CENTER,它将不起作用) 2. 由于此方法只是在我们实际绘制文本时转换文本边界的中心,因此它不适合边界(不考虑文字的升降)
  • @Alex 它做了它该做的事,如果它不符合您的要求,我很抱歉。 “当我们实际绘制文本时它不适合边界”它适合边界,我们只是移动了中心。
【解决方案2】:
textY = topLeftY + height/2 - (mPaint.descent() + mPaint.ascent()) / 2

“基线”到“中心”的距离应该是-(mPaint.descent() + mPaint.ascent()) / 2

【讨论】:

  • 这对我不起作用,但接受的答案非常有效。如果字体更小,这种方法的结果可能会更好。
  • 我在这个问题中尝试了一些技巧,发现你的技巧总体上是最好的。与公认的答案不同,无论您的文本是什么,这个答案都能提供一致的结果。而且与公认的答案不同,您的中心相对于大写字母的中点,我认为这是大多数人所期望的。
【解决方案3】:

根据 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);
}

【讨论】:

    【解决方案4】:

    由于在 Y 处绘制文本意味着文本的基线最终会从原点向下 Y 像素,因此当您想要在 (width, height) 的矩形内居中文本时需要执行什么操作尺寸是:

    paint.setTextAlign(Paint.Align.CENTER);  // centers horizontally
    canvas.drawText(text, width / 2, (height - paint.ascent()) / 2, paint);
    

    请记住,上升是负数(这解释了减号)。

    这没有考虑下降,这通常是你想要的(上升通常是基线以上的大写高度)。

    【讨论】:

    • 我刚刚尝试了这个答案,但发现文本没有像您建议的那样相对于大写字母居中。相反,我还必须使用 descent() 来获得这种行为。
    【解决方案5】:

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

    【讨论】:

    • getTextBounds() 属于 Paint,而不是 Canvas
    • 请记住,当您说垂直居中的文本时,您可能是指围绕字符的上升部分,而不是基线。当您使用 Align.CENTER 绘制时,Y 坐标将以字符基线为中心。
    • @CameronLowellPalmer 你能详细说明一下吗?
    • @Peterdk 我建议您自己尝试一下,但您要测量的是绘制文本的边界。这意味着如果文本下降到基线以下并且您正在排列几个单独绘制的单词,那么您的文本将不会按照您期望的方式排列。单词“grow”下降到基线以下,单词'嗨'没有。因此,如果您找到这两个单词的 TextBounds 的中心,然后除以 2 以找到高度,您将得到不希望的结果。
    【解决方案6】:
    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);
    

    【讨论】:

      【解决方案7】:

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

      在 Kotlin 的情况下:

      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);
      }
      

      【讨论】:

        【解决方案8】:

        这是一个 SkiaSharp C# 扩展方法,任何人都在寻找它

        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);
        }
        

        【讨论】:

          【解决方案9】:
          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);
          }
          

          【讨论】:

          • 请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助、质量更好,并且更有可能吸引投票。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-26
          相关资源
          最近更新 更多