157

Any straight forward way to measure the height of text? The way I am doing it now is by using Paint's measureText() to get the width, then by trial and error finding a value to get an approximate height. I've also been messing around with FontMetrics, but all these seem like approximate methods that suck.

I am trying to scale things for different resolutions. I can do it, but I end up with incredibly verbose code with lots of calculations to determine relative sizes. I hate it! There has to be a better way.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Danedo
  • 2,193
  • 5
  • 29
  • 37

8 Answers8

304

There are different ways to measure the height depending on what you need.

#1 getTextBounds

If you are doing something like precisely centering a small amount of fixed text, you probably want getTextBounds. You can get the bounding rectangle like this

Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();

As you can see for the following images, different strings will give different heights (shown in red).

enter image description here

These differing heights could be a disadvantage in some situations when you just need a constant height no matter what the text is. See the next section.

#2 Paint.FontMetrics

You can calculate the hight of the font from the font metrics. The height is always the same because it is obtained from the font, not any particular text string.

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;

The baseline is the line that the text sits on. The descent is generally the furthest a character will go below the line and the ascent is generally the furthest a character will go above the line. To get the height you have to subtract ascent because it is a negative value. (The baseline is y=0 and y descreases up the screen.)

Look at the following image. The heights for both of the strings are 234.375.

enter image description here

If you want the line height rather than just the text height, you could do the following:

float height = fm.bottom - fm.top + fm.leading; // 265.4297

These are the bottom and top of the line. The leading (interline spacing) is usually zero, but you should add it anyway.

The images above come from this project. You can play around with it to see how Font Metrics work.

#3 StaticLayout

For measuring the height of multi-line text you should use a StaticLayout. I talked about it in some detail in this answer, but the basic way to get this height is like this:

String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";

TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);

int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;

StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);

float height = myStaticLayout.getHeight(); 
Top-Master
  • 7,611
  • 5
  • 39
  • 71
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • Nice explanation. What app are the screenshots from? – micheal65536 Feb 22 '17 at 20:22
  • 2
    @MichealJohnson, I added the app as a [GitHub project here](https://github.com/suragch/AndroidFontMetrics). – Suragch Mar 03 '17 at 00:15
  • 1
    What does "getTextSize" give you, then? – android developer May 13 '18 at 14:34
  • 1
    Paint's [`getTextSize()`](https://developer.android.com/reference/android/graphics/Paint#getTextSize()) gives you the font size in pixel units (as opposed to `sp` units). @androiddeveloper – Suragch May 13 '18 at 23:16
  • 2
    What is the relationship of the font size in pixel units to the measured height and to the [FontMetrics dimensions](https://stackoverflow.com/a/27631737/3681880)? That is a question that I would like to explore more. – Suragch May 13 '18 at 23:24
  • How can you do this in reverse? I have a view of height, say 50dp, and I want to set the text size to match this height so it fills the box. – Jim Leask Nov 22 '18 at 15:29
  • 1
    @JimLeask, You can use [Autosizing TextViews](https://stackoverflow.com/a/52772600/3681880). But if you are drawing text on canvas yourself then you have to set the font, measure it, reset the font, measure it...until you have the desired height. – Suragch Nov 23 '18 at 02:54
  • @Suragch I am drawing directly to the canvas. It is surprising that there isn't an API to get the font for a given height. Finding the font size by trail and error with iterating through values is not a great solution, but it seems that is a missing API in Android so all I can do. – Jim Leask Nov 27 '18 at 01:29
  • @JimLeask, I think the reason there is no API is because the actual height at a certain font size is up to the font developer and there is no standard for what it must be. That is, there is no definite correlation between font size and text height. If you go the trial and error route, you could do a binary search. That is what `TextView` does under the hood. A new idea I saw recently is to convert the text to a path. I think if you had a path you could set the size more directly. I haven't tried it yet, though. – Suragch Nov 27 '18 at 18:04
  • When using FontMetrics, is it sp, dp, or pixels ? – RonTLV Dec 28 '18 at 09:06
  • 1
    @RonTLV, with FontMetrics it is pixels. When you get into lower level graphics rendering like this it is all pixels. In the StaticLayout example above I made the dp conversion by multiplying by the device density. See [this Q&A](https://stackoverflow.com/questions/25752708/what-is-the-unit-for-textpaint-settextsize-in-android) also. – Suragch Dec 28 '18 at 17:13
  • @Suragch - please advise how to calculate text height for styles, such as `@style/TextAppearance.AppCompat.Notification.Title`. **Thank you!** – Ωmega May 21 '19 at 22:51
  • @Ωmega, I'm not sure off hand, but see if you can get a reference to the TextPaint or FontMetrics from whatever object you are working with. – Suragch May 21 '19 at 23:16
  • Very informative. Anything similar for iOS? – jho Jul 26 '22 at 19:15
145

What about paint.getTextBounds() (object method)

Alon
  • 601
  • 9
  • 19
bramp
  • 9,581
  • 5
  • 40
  • 46
  • 2
    This yields very odd results, when I evaluate the height of a text. A short text results in a height of 12, whereas a REALLY long text results in a height of 16 (given a font size of 16). Makes no sense to me (android 2.3.3) – AgentKnopf Feb 20 '12 at 16:07
  • 40
    The variance in height is where you have descenders in the text, i.e. 'High' is taller than 'Low' because of the part of the g below the line – FrinkTheBrave Nov 08 '12 at 13:32
  • If you don't want variances, use [**Paint.FontMetrics**](https://developer.android.com/reference/android/graphics/Paint.FontMetrics.html) instead. – Top-Master May 23 '22 at 09:48
91

@bramp's answer is correct - partially, in that it does not mention that the calculated boundaries will be the minimum rectangle that contains the text fully with implicit start coordinates of 0, 0.

This means, that the height of, for example "Py" will be different from the height of "py" or "hi" or "oi" or "aw" because pixel-wise they require different heights.

This by no means is an equivalent to FontMetrics in classic java.

While width of a text is not much of a pain, height is.

In particular, if you need to vertically center-align the drawn text, try getting the boundaries of the text "a" (without quotes), instead of using the text you intend to draw. Works for me...

Here's what I mean:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);

paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);

Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);

buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster

Vertically center aligning the text means vertically center align the bounding rectangle - which is different for different texts (caps, long letters etc). But what we actually want to do is to also align the baselines of rendered texts, such that they did not appear elevated or grooved. So, as long as we know the center of the smallest letter ("a" for example) we then can reuse its alignment for the rest of the texts. This will center align all the texts as well as baseline-align them.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Nar Gar
  • 2,591
  • 2
  • 25
  • 28
16

The height is the text size you have set on the Paint variable.

Another way to find out the height is

mPaint.getTextSize();
kimnod
  • 181
  • 1
  • 9
3

You could use the android.text.StaticLayout class to specify the bounds required and then call getHeight(). You can draw the text (contained in the layout) by calling its draw(Canvas) method.

intrepidis
  • 2,870
  • 1
  • 34
  • 36
2

You can simply get the text size for a Paint object using getTextSize() method. For example:

Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
            .getDisplayMetrics().density;  
mTextPaint.setTextSize(24.0f*densityMultiplier);

//...

float size = mTextPaint.getTextSize();
moondroid
  • 1,730
  • 17
  • 20
1

You must use Rect.width() and Rect.Height() which returned from getTextBounds() instead. That works for me.

Ant4res
  • 1,217
  • 1
  • 18
  • 36
Putti
  • 11
  • 1
0

If anyone still has problem, this is my code.

I have a custom view which is square (width = height) and I want to assign a character to it. onDraw() shows how to get height of character, although I'm not using it. Character will be displayed in the middle of view.

public class SideBarPointer extends View {

    private static final String TAG = "SideBarPointer";

    private Context context;
    private String label = "";
    private int width;
    private int height;

    public SideBarPointer(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init() {
//        setBackgroundColor(0x64FF0000);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        height = this.getMeasuredHeight();
        width = this.getMeasuredWidth();

        setMeasuredDimension(width, width);
    }

    protected void onDraw(Canvas canvas) {
        float mDensity = context.getResources().getDisplayMetrics().density;
        float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;

        Paint previewPaint = new Paint();
        previewPaint.setColor(0x0C2727);
        previewPaint.setAlpha(200);
        previewPaint.setAntiAlias(true);

        Paint previewTextPaint = new Paint();
        previewTextPaint.setColor(Color.WHITE);
        previewTextPaint.setAntiAlias(true);
        previewTextPaint.setTextSize(90 * mScaledDensity);
        previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));

        float previewTextWidth = previewTextPaint.measureText(label);
//        float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
        RectF previewRect = new RectF(0, 0, width, width);

        canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
        canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);

        super.onDraw(canvas);
    }

    public void setLabel(String label) {
        this.label = label;
        Log.e(TAG, "Label: " + label);

        this.invalidate();
    }
}
Hesam
  • 52,260
  • 74
  • 224
  • 365
  • 4
    -1 for allocations in onDraw(): It would yield a ton of performance benefits if you were to declare the fields in the class (initiate in constructor) and re-use them in onDraw(). – zoltish Oct 31 '15 at 07:45