45

Could not find any good solution calculating textview height where text was set before rendering textview to layout. Any help please

support_ms
  • 1,873
  • 1
  • 18
  • 33
  • there might be a problem in what you are trying to do. if you set the height to a certain value (like, 60dp) ok, but if you set it to "wrap_content" , the height will depend on screen size. so you might be looking to create the textview programmatically (in Java code) – mobilGelistirici Nov 11 '13 at 13:52
  • I am creating e book reader and for that I should know textview's height how much text fits it – support_ms Nov 11 '13 at 13:59
  • If you set it to "wrap_content" , it will expand according to its content. If you want to make sure that the user should not slide vertically, then you may look into viewflipper, viewpager etc. – mobilGelistirici Nov 11 '13 at 14:02
  • Have you checked this [Question] (http://stackoverflow.com/questions/15679147/how-to-get-line-count-of-textview-before-rendering). – Rethinavel Nov 11 '13 at 14:04
  • @mobilGelistirici There is spannable text and different fonts in a text I can not calculate how much text is enough for one page.That's why I split text by spaces and after adding each element it should check if textview did not overcome display height.For that I should know textview's height and check it before rendering every time I add text – support_ms Nov 11 '13 at 14:07
  • @RethinavelPillai it is not depending on line count of textview there are different fonts are using and line spacing is not same – support_ms Nov 11 '13 at 14:09
  • what happens when the user rotates the screen ? If I were you, I would split the book chapter by chapter. Otherwise, adding words one by one, by doing mathematical calculations in each step, would take a long time for the book to load. – mobilGelistirici Nov 11 '13 at 14:14
  • @mobilGelistirici there is begin index of each page if I rotate display it should calculate from begin index how much text is enough for page.There should be solution to know textview's height before rendering on layout , but I could not get it yet :( – support_ms Nov 11 '13 at 14:22
  • Well you could get device screen size. But how would you get the text's size? with different fonts etc. I really think it's not a good idea to calculate a total size from a text consisting of different parts. – mobilGelistirici Nov 11 '13 at 14:28
  • @mobilGelistirici let's say I have TextView tv then if (tv.getTextHeight(mytext.subSequence(beginIndex , whiteSpaces[resume])) > (displayheight - StatusBarHeight - 2 * getPadding())) { setPageTextEndPoint(whiteSpaces[resume - 1]); break; } – support_ms Nov 11 '13 at 14:33
  • getTextHeight? I never heard of that function. If you can define it, then you got your solution :) – mobilGelistirici Nov 11 '13 at 14:35
  • Yeah my textview is custom.I should put code for getTextHeight() method I'm searching for – support_ms Nov 11 '13 at 14:40

6 Answers6

79

2 solutions

Used solution 1 at first and found solution 2 later on. Both work, it's really what you prefer.

Important is to make sure you got all the dimensions right since mixing font sizes in sp or px will give quite a difference depending on what screen you test on.

A very basic example project is available at https://github.com/hanscappelle/SO-3654321

Solution 1 using TextView and MeasureSpec

Main issue with original question is TextView in below method should be configured as our TextView which should be rendered to layout. I think this solution is valuable for many people who faced this problem.

public static int getHeight(Context context, CharSequence text, int textSize, int deviceWidth, Typeface typeface,int padding) {
            TextView textView = new TextView(context);
            textView.setPadding(padding,0,padding,padding);
            textView.setTypeface(typeface);
            textView.setText(text, TextView.BufferType.SPANNABLE);
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
            int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
            int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            textView.measure(widthMeasureSpec, heightMeasureSpec);
            return textView.getMeasuredHeight();
        }

And an example of how to use this:

// retrieve deviceWidth
int deviceWidth;
WindowManager wm = (WindowManager) textView.getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2){
    Point size = new Point();
    display.getSize(size);
    deviceWidth = size.x;
} else {
    deviceWidth = display.getWidth();
}
// the text to check for
String exampleTextToMeasure = "some example text that will be long enough to make this example split over multiple lines so we can't easily predict the final height";
//  some dimensions from dimes resources to take into account
int textSize = getContext().getResources().getDimensionPixelSize(R.dimen.text_size);
int padding = getContext().getResources().getDimensionPixelSize(R.dimen.text_padding);

// final calculation of textView height
int measuredTextHeight = getHeight(getContext(), exampleTextToMeasure, textSize, deviceWidth, TypeFace.DEFAULT, padding); 

Solution 2 using TextPaint and StaticLayout

This method relies on a TextPaint and StaticLayout which also gives reliable results on all API levels I've tested so far. Pay good attention to units of dimensions; all should be in pixels!

Source: Measuring text height to be drawn on Canvas ( Android )

    public static int method1UsingTextPaintAndStaticLayout(
            final CharSequence text,
            final int textSize, // in pixels
            final int deviceWidth, // in pixels
            final int padding // in pixels
    ) {

        TextPaint myTextPaint = new TextPaint();
        myTextPaint.setAntiAlias(true);
        // this is how you would convert sp to pixels based on screen density
        //myTextPaint.setTextSize(16 * context.getResources().getDisplayMetrics().density);
        myTextPaint.setTextSize(textSize);
        Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
        float spacingMultiplier = 1;
        float spacingAddition = padding; // optionally apply padding here
        boolean includePadding = padding != 0;
        StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, deviceWidth, alignment, spacingMultiplier, spacingAddition, includePadding);
        return myStaticLayout.getHeight();
    }
Community
  • 1
  • 1
support_ms
  • 1,873
  • 1
  • 18
  • 33
19

From support_ms answer, there is a more simple method that take only a TextView as parameter.

/**
 * Get the TextView height before the TextView will render
 * @param textView the TextView to measure
 * @return the height of the textView
 */
public static int getTextViewHeight(TextView textView) {
    WindowManager wm =
            (WindowManager) textView.getContext().getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    int deviceWidth;

    if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2){
        Point size = new Point();
        display.getSize(size);
        deviceWidth = size.x;
    } else {
        deviceWidth = display.getWidth();
    }

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    textView.measure(widthMeasureSpec, heightMeasureSpec);
    return textView.getMeasuredHeight();
}
Hugo Gresse
  • 17,195
  • 9
  • 77
  • 119
  • 3
    I've tried this and it almost works. However, the last line of text is getting cut off. I tried testing this and put a lot of very long gibberish words in there >20 chars and the amount of lines getting cut off increases. I can only assume this measured height doesn't take into account word overflow into the next line. So this calculated height is less than the actual rendered height, increasing in disparity with larger words and longer text. – Distraction Apr 30 '15 at 14:42
  • @Distraction I should be something with the EditText makeMeasureSpec or the EditText settings on inflate. – Hugo Gresse Apr 30 '15 at 14:46
  • This one works perfectly, at least for a TextView with a single line. (This is what I need). Notice that because the device's width in actual pixels is often already known (it's often used in other parts of the app), and that the variable "wm" is unused, only the four last lines of this code are sufficient. Great ! – Chrysotribax Mar 01 '16 at 16:41
9

Good answer from @support_ms, but I'm not sure of the point of creating a new TextView and working out all of this input params when you could just format your TextView first and then call the static method with just one parameter, the TextView itself!

Also I'm not sure why one parameter was labelled deviceWidth I just use the width of the Textview itself. Mine was match_parent and I suppose any TextView with wrap_content may not work at all. But that's what you get.

public static int getHeight(TextView t) {
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(screenWidth(t.getContext()), View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    t.measure(widthMeasureSpec, heightMeasureSpec);
    return t.getMeasuredHeight();
}

public static int screenWidth(Context context)
{
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    return display.getWidth();
}
Andrew G
  • 2,596
  • 2
  • 19
  • 26
0

Here is my easy solution its get the size before be painted

https://stackoverflow.com/a/40133275/1240672

Community
  • 1
  • 1
jfcogato
  • 3,359
  • 3
  • 19
  • 19
0

Get line of TextView before rendering

This is my code base on idea above. It's working for me.

private int widthMeasureSpec;
private int heightMeasureSpec;
private int heightOfEachLine;
private int paddingFirstLine;
private void calculateHeightOfEachLine() {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int deviceWidth = size.x;
    widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    //1 line = 76; 2 lines = 76 + 66; 3 lines = 76 + 66 + 66
    //=> height of first line = 76 pixel; height of second line = third line =... n line = 66 pixel
    int heightOfFirstLine = getHeightOfTextView("A");
    int heightOfSecondLine = getHeightOfTextView("A\nA") - heightOfFirstLine;
    paddingFirstLine = heightOfFirstLine - heightOfSecondLine;
    heightOfEachLine = heightOfSecondLine;
}

private int getHeightOfTextView(String text) {
    // Getting height of text view before rendering to layout
    TextView textView = new TextView(context);
    textView.setPadding(10, 0, 10, 0);
    //textView.setTypeface(typeface);
    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.tv_size_14sp));
    textView.setText(text, TextView.BufferType.SPANNABLE);
    textView.measure(widthMeasureSpec, heightMeasureSpec);
    return textView.getMeasuredHeight();
}

private int getLineCountOfTextViewBeforeRendering(String text) {
    return (getHeightOfTextView(text) - paddingFirstLine) / heightOfEachLine;
}

Note: This code also must be set for real textview on screen

textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.tv_size_14sp));
Anh Duy
  • 1,145
  • 15
  • 25
0

Kotlin extension

fun TextView.calculateHeight(text: CharSequence = getText()): Int {
    val alignment = when(gravity) {
        Gravity.CENTER -> Layout.Alignment.ALIGN_CENTER
        Gravity.RIGHT -> Layout.Alignment.ALIGN_OPPOSITE
        else -> Layout.Alignment.ALIGN_NORMAL
    }
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        StaticLayout.Builder.obtain(text, 0, text.length, TextPaint(paint), width)
            .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
            .setAlignment(alignment)
            .setIncludePad(true).build()
    } else {
        @Suppress("DEPRECATION")
        StaticLayout(
            text, TextPaint(paint), width, alignment,
            lineSpacingMultiplier, lineSpacingExtra, true
        )
    }.height
}
KursikS
  • 176
  • 2
  • 8