202

I'm trying to display a text using the code below. The problem is that the text is not centered horizontally. When I set the coordinates for drawText, it sets the bottom of the text at this position. I would like the text to be drawn so that the text is centered also horizontally.

This is a picture to display my problem further:

Screenshot

@Override
protected void onDraw(Canvas canvas) {
    // TODO Auto-generated method stub
    super.onDraw(canvas);
    //canvas.drawRGB(2, 2, 200);
    Paint textPaint = new Paint();
    textPaint.setARGB(200, 254, 0, 0);
    textPaint.setTextAlign(Align.CENTER);
    textPaint.setTypeface(font);
    textPaint.setTextSize(300);
    canvas.drawText("Hello", canvas.getWidth()/2, canvas.getHeight()/2  , textPaint);
}
Pang
  • 8,605
  • 144
  • 77
  • 113
Sebastian
  • 2,428
  • 2
  • 14
  • 25
  • 20
    I wouldn't declare your paint object inside your onDraw event. It gets recreated each time it is redrawn. Consider making it a private class variable. – Christopher Rathgeb Sep 16 '13 at 03:23
  • 9
    Mate! Please mention that your image link is NSFW! I don't mean to be prudish, but I don't need ads with topless women appearing on my screen in the office. – Michael Scheper Feb 19 '14 at 01:05
  • 5
    @MichaelScheper Sorry,I've updated the link! – Sebastian Feb 20 '14 at 12:41
  • 26
    @Sebastian Hey you still got that link? – slaw Oct 03 '16 at 05:32
  • @S.Lukas hhahaha – BOTJr. Dec 26 '17 at 14:28
  • Just a quick answer to why your text was only centered horizontally. Paint.Align.CENTER states that the text will we centered HORIZONTALLY on the point of reference. See the comment here: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android11-gsi/graphics/java/android/graphics/Paint.java#530 – BamsBamx Dec 08 '20 at 17:22

12 Answers12

397

Try the following:

 Paint textPaint = new Paint();
 textPaint.setTextAlign(Paint.Align.CENTER);

 int xPos = (canvas.getWidth() / 2);
 int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ; 
 //((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center.

 canvas.drawText("Hello", xPos, yPos, textPaint);
Denis
  • 2,311
  • 1
  • 15
  • 38
Arun George
  • 17,272
  • 4
  • 25
  • 28
  • 10
    Great answer. For me I used the following as I needed to center the text horizontally fully rather than the text to start at the center position: int xPos = (Width - textPaint.TextSize * Math.Abs(_text.Length / 2)) / 2; Not sure if there's a better way to accomplish this. – paj7777 Apr 08 '13 at 09:06
  • And probably best casting _text.Length to a float as it obviously won't work for odd text lengths. – paj7777 Apr 08 '13 at 09:44
  • 63
    paj7777, that's not necessary if you set textPaint.setTextAlign(Align.CENTER); – Costi Muraru Dec 16 '13 at 13:32
  • @paj7777 That would only have worked for fixed-width fonts anyhow. Also, you don't need to *cast* to a float; if you divide by a float, the result will be a float. e.g. `float halfLength = text.length() / 2f;` This is called type *promotion*. – Michael Scheper Feb 19 '14 at 01:11
  • Arun: Any reason why you're casting to `int`? `Canvas.drawText()` uses `float`s as coordinates, so your text would be more reliably centred if you stuck to those. – Michael Scheper Feb 19 '14 at 01:24
  • 2
    I compared this approach (= center with `Paint.descent()` and `Paint.ascent()`) with the approach to center text with `Paint.getTextBounds()` in my answer below. `Paint.descent()` and `Paint.ascent()` do not take into account the actual text. (You can recognize this inaccuracy in the screenshot in my post below.) That is why I would recommend against this approach. The approach with `Paint.getTextBounds()` seems to work more accurate. – andreas1724 Aug 28 '15 at 21:39
  • You saved my day...Thanks Arun – Shabbir Panjesha May 25 '16 at 10:10
  • I think this one will not work if I specify my View height to match_parent – Anton Kazakov Nov 02 '19 at 20:48
231

Center with Paint.getTextBounds():

enter image description here

private Rect r = new Rect();

private void drawCenter(Canvas canvas, Paint paint, String text) {
    canvas.getClipBounds(r);
    int cHeight = r.height();
    int cWidth = r.width();
    paint.setTextAlign(Paint.Align.LEFT);
    paint.getTextBounds(text, 0, text.length(), r);
    float x = cWidth / 2f - r.width() / 2f - r.left;
    float y = cHeight / 2f + r.height() / 2f - r.bottom;
    canvas.drawText(text, x, y, paint);
}
  • Paint.Align.CENTER doesn't mean that the reference point of the text is vertically centered. The reference point is always on the baseline. So, why not use Paint.Align.LEFT? You have to calculate the reference point anyway.

  • Paint.descent() has the disadvantage, that it doesn't consider the real text. Paint.descent() retrieves the same value, regardless of whether the text contains letters with descents or not. That's why I use r.bottom instead.

  • I have had some problems with Canvas.getHeight() if API < 16. That's why I use Canvas.getClipBounds(Rect) instead. (Do not use Canvas.getClipBounds().getHeight() as it allocates memory for a Rect.)

  • For reasons of performance, you should allocate objects before they are used in onDraw(). As drawCenter() will be called within onDraw() the object Rect r is preallocated as a field here.


I tried to put the code of the two top answers into my own code (August 2015) and made a screenshot to compare the results:

text centered three versions

The text should be centered within the red filled rectangle. My code produces the white text, the other two codes produces altogether the gray text (they are actually the same, overlapping). The gray text is a little bit too low and two much on the right.

This is how I made the test:

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

class MyView extends View {

    private static String LABEL = "long";
    private static float TEXT_HEIGHT_RATIO = 0.82f;

    private FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(0, 0);
    private Rect r = new Rect();
    private Paint paint = new Paint();
    private Paint rectPaint = new Paint();

    public MyView(Context context) {
        super(context);
    }

    private void drawTextBounds(Canvas canvas, Rect rect, int x, int y) {
        rectPaint.setColor(Color.rgb(0, 0, 0));
        rectPaint.setStyle(Paint.Style.STROKE);
        rectPaint.setStrokeWidth(3f);
        rect.offset(x, y);
        canvas.drawRect(rect, rectPaint);
    }

    // andreas1724 (white color):
    private void draw1(Canvas canvas, Paint paint, String text) {
        paint.setTextAlign(Paint.Align.LEFT);
        paint.setColor(Color.rgb(255, 255, 255));
        canvas.getClipBounds(r);
        int cHeight = r.height();
        int cWidth = r.width();
        paint.getTextBounds(text, 0, text.length(), r);
        float x = cWidth / 2f - r.width() / 2f - r.left;
        float y = cHeight / 2f + r.height() / 2f - r.bottom;
        canvas.drawText(text, x, y, paint);
        drawTextBounds(canvas, r, (int) x, (int) y);
    }

    // Arun George (light green color):
    private void draw2(Canvas canvas, Paint textPaint, String text) {
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(Color.argb(100, 0, 255, 0));
        int xPos = (canvas.getWidth() / 2);
        int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2));
        canvas.drawText(text, xPos, yPos, textPaint);
    }

    // VinceStyling (light blue color):
    private void draw3(Canvas yourCanvas, Paint mPaint, String pageTitle) {
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setColor(Color.argb(100, 0, 0, 255));
        r = yourCanvas.getClipBounds();
        RectF bounds = new RectF(r);
        bounds.right = mPaint.measureText(pageTitle, 0, pageTitle.length());
        bounds.bottom = mPaint.descent() - mPaint.ascent();
        bounds.left += (r.width() - bounds.right) / 2.0f;
        bounds.top += (r.height() - bounds.bottom) / 2.0f;
        yourCanvas.drawText(pageTitle, bounds.left, bounds.top - mPaint.ascent(), mPaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int margin = 10;
        int width = w - 2 * margin;
        int height = h - 2 * margin;
        params.width = width;
        params.height = height;
        params.leftMargin = margin;
        params.topMargin = margin;
        setLayoutParams(params);
        paint.setTextSize(height * TEXT_HEIGHT_RATIO);
        paint.setAntiAlias(true);
        paint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.BOLD_ITALIC));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.rgb(255, 0, 0));
        draw1(canvas, paint, LABEL);
        draw2(canvas, paint, LABEL);
        draw3(canvas, paint, LABEL);
    }
}

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        FrameLayout container = new FrameLayout(this);
        container.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        container.addView(new MyView(this));
        setContentView(container);
    }
}
Community
  • 1
  • 1
andreas1724
  • 2,653
  • 1
  • 9
  • 9
67

Align vertically is difficult because text descent and ascent happened, lots of guys used Paint.getTextBounds() to retrieve the TextWidth and TextHeight, but it doesn't make the text center very much. Here we can use Paint.measureText() to calculate the TextWidth, the TextHeight we simply do subtracting with descent and ascent, then we got the most approach TextSize, the following work is fairly easy for each other.

// the Paint instance(should be assign as a field of class).
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(getResources().getDimension(R.dimen.btn_textsize));

// the display area.
Rect areaRect = new Rect(0, 0, 240, 60);

// draw the background style (pure color or image)
mPaint.setColor(Color.BLACK);
yourCanvas.drawRect(areaRect, mPaint);

String pageTitle = "文字小说";

RectF bounds = new RectF(areaRect);
// measure text width
bounds.right = mPaint.measureText(pageTitle, 0, pageTitle.length());
// measure text height
bounds.bottom = mPaint.descent() - mPaint.ascent();

bounds.left += (areaRect.width() - bounds.right) / 2.0f;
bounds.top += (areaRect.height() - bounds.bottom) / 2.0f;

mPaint.setColor(Color.WHITE);
yourCanvas.drawText(pageTitle, bounds.left, bounds.top - mPaint.ascent(), mPaint);

screen shot by the code

By the way, we highly recommend use RectF rather than Rect because the positions need more accurate values, in my experience, RectF done the top&bottom deviation just one pixel on xhdpi device, Rect would be two more.

VinceStyling
  • 3,518
  • 2
  • 26
  • 41
17

Your code is drawing the center of the baseline of the text, at the center of the view. In order to center the text at some point, x, y, you need to calculate the center of the text, and put that at the point.

This method will draw text centered at the point x, y. If you pass it the center of your view, it will draw the text centered.

private void drawTextCentered(String text, int x, int y, Paint paint, Canvas canvas) {
    int xPos = x - (int)(paint.measureText(text)/2);
    int yPos = (int) (y - ((textPaint.descent() + textPaint.ascent()) / 2)) ;

    canvas.drawText(text, xPos, yPos, textPaint);
}
G. Blake Meike
  • 6,323
  • 3
  • 21
  • 38
Borislav Markov
  • 857
  • 7
  • 10
4

I find that the best solution for centering text is as follows:

textPaint.setTextAlign(Paint.Align.CENTER);
//textPaint is the Paint object being used to draw the text (it must be initialized beforehand)
float textY=center.y;
float textX=center.x; 
// in this case, center.x and center.y represent the coordinates of the center of the rectangle in which the text is being placed
canvas.drawText(text,textX,textY,textPaint);    `
Daniel
  • 1,159
  • 1
  • 11
  • 18
  • 1
    This looks exactly like the way, the questioner tried to center the text. The text will not be centered vertically because **Paint.Align.Center** doesn't mean, that the reference point of the text is vertically centered. – andreas1724 Aug 21 '15 at 10:06
3

works for me to use: textPaint.textAlign = Paint.Align.CENTER with textPaint.getTextBounds

private fun drawNumber(i: Int, canvas: Canvas, translate: Float) {
            val text = "$i"
            textPaint.textAlign = Paint.Align.CENTER
            textPaint.getTextBounds(text, 0, text.length, textBound)

            canvas.drawText(
                    "$i",
                    translate + circleRadius,
                    (height / 2 + textBound.height() / 2).toFloat(),
                    textPaint
            )
        }

result is:

enter image description here

Serg Burlaka
  • 1,867
  • 19
  • 31
1

I create a method to simplify this:

    public static void drawCenterText(String text, RectF rectF, Canvas canvas, Paint paint) {
    Paint.Align align = paint.getTextAlign();
    float x;
    float y;
    //x
    if (align == Paint.Align.LEFT) {
        x = rectF.centerX() - paint.measureText(text) / 2;
    } else if (align == Paint.Align.CENTER) {
        x = rectF.centerX();
    } else {
        x = rectF.centerX() + paint.measureText(text) / 2;
    }
    //y
    metrics = paint.getFontMetrics();
    float acent = Math.abs(metrics.ascent);
    float descent = Math.abs(metrics.descent);
    y = rectF.centerY() + (acent - descent) / 2f;
    canvas.drawText(text, x, y, paint);

    Log.e("ghui", "top:" + metrics.top + ",ascent:" + metrics.ascent
            + ",dscent:" + metrics.descent + ",leading:" + metrics.leading + ",bottom" + metrics.bottom);
}

rectF is the area you want draw the text,That's it. Details

Seth
  • 725
  • 7
  • 15
1

This worked for me :

 paint.setTextAlign(Paint.Align.CENTER);
        int xPos = (newWidth / 2);
        int yPos = (newHeight / 2);
        canvas.drawText("Hello", xPos, yPos, paint);

if anyone finds any problem please ket me know

JSONParser
  • 1,071
  • 2
  • 12
  • 27
1

In my case, I didn't have to put the text in the middle of a canvas, but in a wheel that spins. Though I had to use this code to succeed:

fun getTextRect(textSize: Float, textPaint: TextPaint, string: String) : PointF {
    val rect = RectF(left, top, right, bottom)
    val rectHeight = Rect()
    val cx = rect.centerX()
    val cy = rect.centerY()

    textPaint.getTextBounds(string, 0, string.length, rectHeight)
    val y = cy + rectHeight.height()/2
    val x = cx - textPaint.measureText(string)/2

    return PointF(x, y)
}

Then I call this method from the View class:

private fun drawText(canvas: Canvas, paint: TextPaint, text: String, string: String) {
    val pointF = getTextRect(paint.textSize, textPaint, string)
    canvas.drawText(text, pointF!!.x, pointF.y, paint)
}
1

Use this in your paint properties:

 textPaint.setTextAlign(Paint.Align.CENTER);
Amir Hossein Ghasemi
  • 7,952
  • 4
  • 43
  • 42
0

If we are using Static layout

mStaticLayout = new StaticLayout(mText, mTextPaint, mTextWidth,
                Layout.Alignment.ALIGN_CENTER, 1.0f, 0, true);

Layout.Alignment.ALIGN_CENTER this will do the trick. Static layout also has got a lot of other advantages.

Reference:Android Documentation

hushed_voice
  • 1,836
  • 20
  • 41
0

Add these to your onDraw method:

paint.setColor(getContext().getResources().getColor(R.color.black));
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("Text", (float) getHeight() / 2f, (float) getWidth() / 2f, paint);
mindrex
  • 81
  • 1
  • 1