Shai
Shai

Reputation: 25595

Measure height of multi-line TextView before rendering

What i've tried to far:

  1. Calling measure()

    tv.measure(0, 0);
    int height = tv.getMeasuredHeight();
    
  2. Calling measure() with specified sizes/modes

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(99999, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    tv.measure(widthMeasureSpec, heightMeasureSpec);
    int height = tv.getMeasuredHeight();
    
  3. Calling getTextBounds()

    Rect bounds = new Rect();
    tv.getPaint().getTextBounds(tv.getText().toString(), 0, tv.getText().length(), bounds);
    int height = bounds.height();
    
  4. Calling measure() and then calling getTextBounds()

  5. Calling getLineCount() * getLineHeight()

None seem to work. They all return incorrect values (container view gets incorrect height - it's either too small or too large)

Ideas on how to calculate this simple thing??

Upvotes: 6

Views: 3521

Answers (4)

Ferran Maylinch
Ferran Maylinch

Reputation: 11539

You need to specify the available width so the height can be properly calculated.

Note: In cases where you just need to get the height of a view that is already drawn, use ViewTreeObserver. See this question for a thing to consider in that case.

This is why I do, in a piece of code where I want to scale a view from hidden to its full necessary height:

int availableWidth = getParentWidth(viewToScale);

int widthSpec = View.MeasureSpec.makeMeasureSpec(availableWidth, View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

viewToScale.measure(widthSpec, heightSpec);
int measuredHeight = viewToScale.getMeasuredHeight();
// Offtopic: Now I animate the height from 1 to measuredHeight (0 doesn't work)

You may pass the availableWidth yourself, but I calculate it from the parent:

private int getParentWidth(View viewToScale)
{
    final ViewParent parent = viewToScale.getParent();
    if (parent instanceof View) {
        final int parentWidth = ((View) parent).getWidth();
        if (parentWidth > 0) {
            return parentWidth;
        }
    }

    throw new IllegalStateException("View to scale must have parent with measured width");
}

Upvotes: 5

Gabriel Morin
Gabriel Morin

Reputation: 2190

I had a similar problem recently, trying to measure the height of a ViewGroup containing several multiline TextView.

What worked for me was to measure() the TextView, then add (lineCount-1)*lineHeight to its measuredHeight.

Here is the code to measure only one TextView :

private int getMeasuredHeight(TextView textView) {
    textView.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    int height = textView.getMeasuredHeight();
        height += (textView.getLineCount()-1) * textView.getLineHeight();
    return height;
}

And in the case of a ViewGroup with many TextViews, here is my code :

private int getMeasuredHeight(ViewGroup viewGroup) {
    viewGroup.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    int height = viewGroup.getMeasuredHeight();
    for(TextView textView : findAllTextView(viewGroup)) {
        height += (textView.getLineCount()-1) * textView.getLineHeight();
    }
    return height;
}

private List<TextView> findAllTextView(ViewGroup v) {
    List<TextView> result = new ArrayList<>();
    for (int i = 0; i < v.getChildCount(); i++) {
        Object child = v.getChildAt(i);
        if (child instanceof TextView)
            result.add((TextView) child);
        else if (child instanceof ViewGroup)
            for(TextView tv: findAllTextView((ViewGroup) child))
                result.add(tv);
    }
    return result;
}

Upvotes: -1

aga
aga

Reputation: 29454

What you can do here is get ViewTreeObserver associated with this TextView and add OnGlobalLayoutListener to it:

final ViewTreeObserver vto = textView.getViewTreeObserver();

vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        // textView dimensions are calculated at this stage, but textView
        // isn't rendered yet. Do what you need to do and remove OnGlobalLayoutListener
        // after

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        } else {
            textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    }
}

Upvotes: 1

EE66
EE66

Reputation: 4661

Where are you calling those methods? The best way to do what you are trying to do is to use ViewTreeObserver and add onPreDrawListener. Take a look at this i think it will help

Upvotes: 1

Related Questions