Эллипсизация не работает должным образом для многострочного TextView с произвольной максимальной высотой

У меня есть TextView с неизвестной максимальной высотой, которая зависит от разрешения устройства DPI/экрана. Так, например, на устройстве и в MDPI эта максимальная высота позволяет показывать только по 2 строки за раз, значение, которое может быть увеличено до номера undefined.

Моя проблема связана с функциональностью эллипсиса. Предположим, что определенное устройство позволяет отображать 4 строки. Если я вручную установил максимальное количество строк, например...

<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" />

... все работает нормально. Если текст не подходит должным образом, то в конце четвертой строки добавляется многоточие, например:

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

Но я бы предпочел не устанавливать количество строк как статическую переменную, так как я бы предпочел включить поддержку любой комбинации разрешения DPI/экрана. Поэтому, если я удаляю maxLines, эллипс больше не отображается правильно в строке 4, показывая неполную часть текста:

This is some
really really
really really
really long

Если я немного увеличиваю размер TextView, я вижу, что остальная часть текста по-прежнему рисуется "за" другим Views. Установка переменной maxHeight тоже не работает.

Я действительно не могу найти решение этой проблемы. Есть идеи? Если это помогает, я работаю только с Android v4.0.3 и выше (API-уровень 15).

Ответ 1

Рассчитайте, сколько строк вставляется в TextView с TextView#getHeight() и TextView#getLineHeight(). Затем вызовите 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);
    }
});

Ответ 2

Принятый ответ хорошо работает до API 27. Однако, поскольку API 28, если высота строки не была задана (одним из следующих способов), по умолчанию Android добавляет дополнительный интервал между строками, но не после последней строки.

  1. Установите атрибут android:lineHeight=... (документация) в XML макета
  2. Вызывает textView.setLineHeight(...) в вашем исходном коде.

Чтобы узнать новую высоту строки для API 28 и выше, я использовал textView.getLineBounds().

Котлин

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);
    }
});