Как использовать настраиваемое многоточие в Android TextView

У меня есть TextView с maxlines=3, и я хотел бы использовать свое собственное многоточие вместо

"Lore ipsum ..."

я нуждаюсь

"Lore ipsum ... [See more]"

чтобы дать пользователю понять, что при нажатии на представление будет развернут полный текст.

Является ли это возможным ?

Я думал проверить, есть ли в TextView многоточие, и в таком случае добавить текст «[Подробнее]» и после этого установить многоточие непосредственно перед этим, но я не мог найти способ сделать это.

Может быть, если я найду место, где текст обрезан, я могу отключить многоточие и сделать подстроку, а затем добавить «... [Подробнее]», но опять же я не знаю, как получить эту позицию.


person jmhostalet    schedule 26.09.2014    source источник
comment
См. l.getEllipsisStart()   -  person Pedro Oliveira    schedule 26.09.2014


Ответы (5)


Я, наконец, справился с этим (может быть, не самым лучшим):

private void setLabelAfterEllipsis(TextView textView, int labelId, int maxLines){

    if(textView.getLayout().getEllipsisCount(maxLines-1)==0) {
        return; // Nothing to do
    }

    int start = textView.getLayout().getLineStart(0);
    int end = textView.getLayout().getLineEnd(textView.getLineCount() - 1);
    String displayed = textView.getText().toString().substring(start, end);
    int displayedWidth = getTextWidth(displayed, textView.getTextSize());

    String strLabel = textView.getContext().getResources().getString(labelId);
    String ellipsis = "...";
    String suffix = ellipsis + strLabel;

    int textWidth;
    String newText = displayed;
    textWidth = getTextWidth(newText + suffix, textView.getTextSize());

    while(textWidth>displayedWidth){
        newText = newText.substring(0, newText.length()-1).trim();
        textWidth = getTextWidth(newText + suffix, textView.getTextSize());
    }

    textView.setText(newText + suffix);
}

private int getTextWidth(String text, float textSize){
    Rect bounds = new Rect();
    Paint paint = new Paint();
    paint.setTextSize(textSize);
    paint.getTextBounds(text, 0, text.length(), bounds);

    int width = (int) Math.ceil( bounds.width());
    return width;
}
person jmhostalet    schedule 29.09.2014
comment
вместо использования трех точек, как в ellipsis = "...";, вы должны использовать символ ГОРИЗОНТАЛЬНЫЙ ЭЛЛИПСИС (… сущность -> ) - person Marcin Orlowski; 29.09.2014
comment
textView.getLayout() возвращает ноль - person Shahjahan Khan; 17.06.2015
comment
Android использует TextUtils.ELLIPSIS_NORMAL[0] для стандартного многоточия, которое преобразуется в '\u2026'. Это можно найти в Layout#getEllipsisChar, который TextView использует косвенно. - person gMale; 29.12.2015
comment
Пробовал использовать этот способ в recyclerView. textView.getLayout() возвращает значение null. Нужна ли какая-либо модификация, чтобы сделать ее полезной с представлениями переработчика/списка? - person Max Droid; 03.01.2017
comment
если для getLayout используется значение null, используйте ViewTreeObserver.OnGlobalLayoutListener. - person asozcan; 29.03.2017
comment
Хорошее решение, оно хорошо работает, и в итоге оно стало основой решения, которое я в итоге использовал. Однако я вижу три проблемы с этим: способ проверки, если текст имеет эллиптический размер, не самый лучший и может вызвать сбои (см. stackoverflow.com/questions/4005933/), ваш код за это время может произойти сбой, если суффикс никогда не подходит, и я считаю, что вы должны рассматривать всю ширину TextView как доступную ширину, а не ширину отображаемого текста. - person Fred Porciúncula; 08.11.2017

Я думаю, что ответ от @jmhostalet снизит производительность (особенно при работе со списками и большим количеством TextView), потому что TextView рисует текст более одного раза. Я создал собственный TextView, который решает эту проблему в onMeasure() и поэтому рисует текст только один раз.

Первоначально я разместил свой ответ здесь: https://stackoverflow.com/a/52589927/1680301

А вот ссылка на репозиторий: https://github.com/TheCodeYard/EllipsizedTextView

person Georgios    schedule 01.10.2018
comment
да, я делал это в представлении переработчика. это ухудшило все представление, - person JSONParser; 16.12.2018

@George @jmhostalet я делал это в своем представлении переработчика, и это ухудшило всю производительность. `

ViewTreeObserver vto = previewContent.getViewTreeObserver();
    vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                try {
                    Layout layout = previewContent.getLayout();
                    int index1 = layout.getLineStart(previewContent.getMaxLines());
                    if (index1 > 10 && index1 < ab.getPreviewContent().length()) {
                        String s =
                                previewContent.getText().toString().substring(0, index1 - 10);
                        previewContent
                                .setText(Html.fromHtml(
                                        s + "<font color='#DC5530'>...और पढ़ें</font>"));
                    }
                    return true;
                }catch (Exception e)
                {
                    Crashlytics.logException(e);
                }
                return true;
            }
        });` 
person JSONParser    schedule 16.12.2018

Вот хороший способ сделать это с расширением Kotlin. Обратите внимание, что нам нужно дождаться макета представления, прежде чем мы сможем измерить и добавить суффикс.

In TextViewExtensions.kt

fun TextView.setEllipsizedSuffix(maxLines: Int, suffix: String) {
    addOnLayoutChangeListener(object: View.OnLayoutChangeListener {
        override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom:     Int) {

            val allText = text.toString()
            var newText = allText
            val tvWidth = width
            val textSize = textSize

            if(!TextUtil.textHasEllipsized(newText, tvWidth, textSize, maxLines)) return

            while (TextUtil.textHasEllipsized(newText, tvWidth, textSize, maxLines)) {
                newText = newText.substring(0, newText.length - 1).trim()
            }

            //now replace the last few chars with the suffix if we can
            val endIndex = newText.length - suffix.length - 1 //minus 1 just to make sure we have enough room
            if(endIndex > 0) {
                newText = "${newText.substring(0, endIndex).trim()}$suffix"
            }

            text = newText

            removeOnLayoutChangeListener(this)
        }
    })
}

In TextUtil.kt

fun textHasEllipsized(text: String, tvWidth: Int, textSize: Float, maxLines: Int): Boolean {
    val paint = Paint()
    paint.textSize = textSize
    val size = paint.measureText(text).toInt()

    return size > tvWidth * maxLines
}

Затем на самом деле использовать его вот так myTextView.setEllipsizedSuffix(2, "...See more")


Примечание. Если ваш текст поступает с сервера и может содержать символы новой строки, вы можете использовать этот метод, чтобы определить, имеет ли текст многоточие.

fun textHasEllipsized(text: String, tvWidth: Int, textSize: Float, maxLines: Int): Boolean {
    val paint = Paint()
    paint.textSize = textSize
    val size = paint.measureText(text).toInt()
    val newLineChars = StringUtils.countMatches(text, "\n")

    return size > tvWidth * maxLines || newLineChars >= maxLines
}

StringUtils из implementation 'org.apache.commons:commons-lang3:3.4'

person Markymark    schedule 23.08.2019

Вот решение для Котлина.

yourTextView.post{} необходим, потому что текстовое представление не будет иметь эллиптический размер до тех пор, пока оно не будет отрисовано.

  val customSuffix = "... [See more]"
  yourTextView.post {
    if (yourTextView.layout.getEllipsisStart(-1) != -1) {
      val newText = yourTextView.text.removeRange(
          yourTextView.layout.getEllipsisStart(-1) - customSuffix.length, yourTextView.text.length
      )
      yourTextView.text = String.format("%s%s", newText, customSuffix)
    }
  }
person mskolnick    schedule 04.12.2019
comment
Убедитесь, что вы заменили yourTextView текстовым представлением, которое вы пытаетесь сделать многоточием. - person mskolnick; 19.02.2020
comment
да, я использую текстовое представление. но он работает при использовании внутри обработчика - person Muhammed Haris; 19.02.2020
comment
@Xenolion Я не тестировал его на многострочный, но не вижу причин, по которым это не сработает. - person mskolnick; 26.06.2020
comment
Причина, о которой я думаю, заключается в том, что layout.getEllipsisStart(line) дает вам начало многоточия для определенной строки, но не для всего текста! - person Xenolion; 26.06.2020
comment
Может быть. Я бы сказал, попробуйте и посмотрите. - person mskolnick; 26.06.2020