14

I am trying to calculate the width of a multiline text paragraph. To my knowledge, the only class that can do this in Android is the StaticLayout (or DynamicLayout) class. When using this class i do no get the proper length of my text snippet but rather the measured the dimensions are sometimes smaller and sometimes greater depending on the text size.

So i am basically looking for a way to reliably measure the width of a multiline text string.

The following image shows how the measured width diverges from the actual text length in various text sizes.Diverging text and measure

The screenshot is created running the the following code in a custom View:

@Override
protected void onDraw( Canvas canvas ) {
  for( int i = 0; i < 15; i++ ) {
    int startSize = 10;
    int curSize = i + startSize;
    paint.setTextSize( curSize );
    String text = i + startSize + " - " + TEXT_SNIPPET;
    layout = new StaticLayout( text,
                               paint,
                               Integer.MAX_VALUE,
                               Alignment.ALIGN_NORMAL,
                               1.0f,
                               0.0f,
                               true );

    float top = STEP_DISTANCE * i;
    float measuredWidth = layout.getLineMax( 0 );
    canvas.drawRect( 0, top, measuredWidth, top + curSize, bgPaint );
    canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, paint );
  }
}
Moritz
  • 10,124
  • 7
  • 51
  • 61
  • 2
    Have you tried `Paint.measureText()`? – neevek Jun 01 '12 at 14:47
  • http://stackoverflow.com/questions/7549182/android-paint-measuretext-vs-gettextbounds – nullpotent Jun 01 '12 at 14:50
  • @Neevek it yields the same result but on top it is not able to measure multi line text. – Moritz Jun 01 '12 at 14:51
  • @AljoshaBre I know about that question. I added a post at the bottom a long time ago. The solution proposed does not apply to multi line text though. – Moritz Jun 01 '12 at 14:54
  • How come it doesn't apply? Just sum the widths. Seems to me `getTextBounds()` works. See the accepted answer, measurement (red rect) looks about right. – nullpotent Jun 01 '12 at 14:59
  • @AljoshaBre When you put a \n inside your text it does not wrap when measuring via Paint. – Moritz Jun 01 '12 at 15:01
  • I know. But you can always split the string and calc individual width and it to the sum. Can't you? – nullpotent Jun 01 '12 at 15:02
  • @AljoshaBre in case i wanted to only break at \n that would be possible but cumbersome. In addition if i want to limit the max width of the text to lets say 500, my actual text would not be 500px wide but more like 450. I can not easily measure those lines because i wouldn't know where the text starts to wrap. – Moritz Jun 01 '12 at 15:08

3 Answers3

1

You could try using get text bounds.

private int calculateWidthFromFontSize(String testString, int currentSize)
{
    Rect bounds = new Rect();
    Paint paint = new Paint();
    paint.setTextSize(currentSize);
    paint.getTextBounds(testString, 0, testString.length(), bounds);

    return (int) Math.ceil( bounds.width());
}

private int calculateHeightFromFontSize(String testString, int currentSize)
{
    Rect bounds = new Rect();
    Paint paint = new Paint();
    paint.setTextSize(currentSize);
    paint.getTextBounds(testString, 0, testString.length(), bounds);

    return (int) Math.ceil( bounds.height());
}
SashiOno
  • 184
  • 2
  • 7
1

I had a similar issue where the measured text width was off by a bit, once you set a Typeface to the paint this will go away. So before you use the paint object in the StaticLayout just set the Typeface:

textPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL))

or whatever typeface you want.

AmeyaB
  • 989
  • 8
  • 12
1

Here's a way where you can get the multiline width of any text length. usableButtonWidth can be a given text space. I used usableButtonWidth = width of the button - padding. But any default value can be used.

Using StaticLayout, you can get the height of the text for the usableButtonWidth. Then I just try to reduce the available width by decreasing it by 1px in a loop. If the height increases then we come to know that the previously used width was the maximum permissible.

Return this value as the width of multiline text.

private int getTextWidth(){
    if(this.getText().length() != 0){
        Paint textPaint = new Paint();
        Rect bounds = new Rect();
        textPaint.setTextSize(this.getTextSize());
        if(mTypeface != null){
            textPaint.setTypeface(mTypeface);
        }
        String buttonText = this.getText().toString();
        textPaint.getTextBounds(buttonText, 0, buttonText.length(), bounds);

        if(bounds.width() > usableButtonWidth){
            TextPaint longTextPaint = new TextPaint();
            longTextPaint.setTextSize(this.getTextSize());
            if(mTypeface != null){
                longTextPaint.setTypeface(mTypeface);
            }
            StaticLayout staticLayout = new StaticLayout(this.getText(), longTextPaint, usableButtonWidth,
                    Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
            int textLineCount = (int)Math.ceil(staticLayout.getHeight() / bounds.height());
            int multiLineTextWidth = usableButtonWidth;
            while ((int)Math.ceil(staticLayout.getHeight() / bounds.height()) == textLineCount && multiLineTextWidth > 1){
                multiLineTextWidth--;
                staticLayout = new StaticLayout(this.getText(), longTextPaint, multiLineTextWidth,
                        Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
            }
            return multiLineTextWidth+1;
        } else {
            return bounds.width();
        }
    } else {
        return 0;
    }
}
suku
  • 10,507
  • 16
  • 75
  • 120