android – Ellipsize not working properly for a multiline TextView with an arbitrary maximum height-ThrowExceptions

Exception or error:

I have a TextView with an unknown maximum height, which is dependent on the device’s DPI/screen resolution. So, for instance, on and MDPI device this maximum height makes it possible to show only 2 lines at a time, a value that can be increased up to an undefined number.

My issue is related with the ellipsize functionality. Let’s suppose that a certain device allows for 4 lines to be displayed. If I manually set the maximum number of lines, like this…

<TextView
    android:id="@+id/some_id"
    android:layout_width="fill_parent"
    android:layout_height="0dip"
    android:ellipsize="end" 
    android:maxLines="4"
    android:singleLine="false"
    android:layout_weight="1"
    android:gravity="center_vertical"
    android:text="This is some really really really really really long text"
    android:textSize="15sp" />

…everything works OK. If the text doesn’t fit properly, then the ellipsis are added at the end of the fourth line, like this:

This is some
really really
really really
really long...

But I’d rather not set the number of lines as a static variable, as I would prefer to include support for any combination of DPI/screen resolution. So if I remove maxLines the ellipsis is no longer correctly shown at line four, showing instead an incomplete portion of text:

This is some
really really
really really
really long

If I slightly increase the TextView size, I can see that the rest of the text is still being drawn “behind” the other Views. Setting the variable maxHeight doesn’t seem to work either.

I really can’t seem to find a solution for this issue. Any ideas? If it helps, I’m working only with Android v4.0.3 and up (API level 15).

How to solve:

Calculate how many lines fit into the TextView with TextView#getHeight() and TextView#getLineHeight(). Then call TextView#setMaxLines().

ViewTreeObserver observer = textView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int maxLines = (int) textView.getHeight()
                / textView.getLineHeight();
        textView.setMaxLines(maxLines);
        textView.getViewTreeObserver().removeGlobalOnLayoutListener(
                this);
    }
});

###

The accepted answer works well up to API 27. However, since API 28, if the line height was not set (by one of the follow methods), by default Android adds extra spacing between lines, but not after the last line.

  1. Set attribute android:lineHeight=... (documentation) in your layout XML
  2. Calls textView.setLineHeight(...) in your source code.

To find out the new line height for API 28 and above, I used textView.getLineBounds().

Kotlin

val observer = textView?.viewTreeObserver
observer?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
    override fun onGlobalLayout() {
        textView?.let { view ->
            val lineHeight: Int
            lineHeight = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                val bounds = Rect()
                textView.getLineBounds(0, bounds)
                bounds.bottom - bounds.top
            } else {
                textView.lineHeight
            }
            val maxLines = textView.height / lineHeight
            textView.maxLines = maxLines
            textView.viewTreeObserver.removeOnGlobalLayoutListener(this)
        }
    }
})

Android Java

ViewTreeObserver observer = textView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int lineHeight = textView.getLineHeight();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            Rect bounds = new Rect();
            textView.getLineBounds(0, bounds);
            lineHeight = bounds.bottom - bounds.top;
        }
        int maxLines = (int) textView.getHeight() / lineHeight;
        textView.setMaxLines(maxLines);
        textView.getViewTreeObserver().removeGlobalOnLayoutListener(
            this);
    }
});

Leave a Reply

Your email address will not be published. Required fields are marked *