Подтвердить что ты не робот

Как выровнять текст по вертикали?

Цель: 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 + height/2 + fontHeight/2 - fontHeight/8;

похоже, работает более или менее нормально, но должен быть лучший способ.

4b9b3361

Ответ 1

Пример для центра на cx и cy:

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

Это даст только тот же результат, когда top равен 0. Это может иметь место для некоторых шрифтов любого размера или других шрифтов с некоторыми размерами, но не для всех шрифтов любого размера.

Ответ 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);

Имейте в виду, что восхождение отрицательное (что объясняет знак минуса).

Это не учитывает спуск, обычно это то, что вы хотите (восхождение, как правило, высоты шапки выше базовой линии).

Ответ 5

используя mPaint.getTextBounds(), вы можете спросить, насколько велика будет текст при рисовании, а затем используя эту информацию, которую вы можете вычислить, где вы хотите ее нарисовать.

Ответ 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 С# для всех, кто его ищет

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

Я знаю, что это довольно старый, но это сработало для меня. В основном создайте SKPath для текста, затем используйте высоту от этого. Таким образом, он фактически измеряет точный текст, а не только шрифт в целом.

SKImageInfo info = e.Info;

...

using (SKPaint textPaint = new SKPaint())
{
    textPaint.TextSize = 40;
    textPaint.Shader = SKShader.CreateColor(SKColors.White);
    textPaint.BlendMode = SKBlendMode.SrcOver;
    textPaint.IsAntialias = true;

    float maxHeight = (textPaint.FontMetrics.Top * -1);
    float length = textPaint.MeasureText(Text);
    float leftPos = info.Width / 2 - length / 2;

    SKPath textPath = textPaint.GetTextPath(Text, 0, 0);
    float topPos = info.Height / 2 + textPath.Bounds.Height / 2;

    canvas.DrawText(Text, new SKPoint(leftPos, topPos), textPaint);
}