Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reliably determine the width of a multi line string?

Tags:

android

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 );
  }
}
like image 203
Moritz Avatar asked Jun 01 '12 14:06

Moritz


3 Answers

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());
}
like image 131
SashiOno Avatar answered Nov 09 '22 02:11

SashiOno


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.

like image 23
AmeyaB Avatar answered Nov 09 '22 01:11

AmeyaB


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;
    }
}
like image 1
suku Avatar answered Nov 09 '22 02:11

suku