How to use custom ellipsis in Android TextView
Asked Answered
E

5

15

I have a TextView with maxlines=3 and I would like to use my own ellipsis, instead of

"Lore ipsum ..."

I need

"Lore ipsum ... [See more]"

in order to give the user a clue that clicking on the view is going to expand the full text.

Is it possible ?

I was thinking about check whether TextView has ellipsis and in such a case add the text "[See more]" and after that set ellipsis just before, but I couldn't find the way to do that.

Maybe if I find the position where the text is cutted, I can disable the ellipsis and make a substring and later add "... [See more]", but again I dont know how to get that position.

Engine answered 26/9, 2014 at 7:57 Comment(1)
Refer to l.getEllipsisStart()Homes
E
8

I've finally managed it in this way (may be not the best one):

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;
}
Engine answered 29/9, 2014 at 11:41 Comment(6)
instead of using three dots like in ellipsis = "..."; you should use HORIZONTAL ELLIPSIS character (… entity -> )Doe
textView.getLayout() returning nullLallygag
Android uses TextUtils.ELLIPSIS_NORMAL[0] for standard ellipsis, which resolves to '\u2026'. This can be found in the Layout#getEllipsisChar method that TextView uses, indirectly.Commemorate
Tried using this way in recyclerView. textView.getLayout() returns null. Is there ay modification needed to make it useful with recycler/ list views?Salangi
if having null for getLayout use ViewTreeObserver.OnGlobalLayoutListener.Dilorenzo
Nice solution, it works well and it ended up being the foundation of the solution I ended up using. I see three issues with it, though: the way you test if the text is ellipsized isn't the best and might cause crashes (see #4006433), your code within the while might crash in case the suffix never fits, and I believe you should consider the whole width of the TextView as the available width, instead of the width of the displayed text.Isallobar
S
2

I think the answer from @jmhostalet will degrade the performance (especially when dealing with lists and lots of TextViews) because the TextView draws the text more than once. I've created a custom TextView that solves this in the onMeasure() and therefore only draws the text once.

I've originally posted my answer here: https://mcmap.net/q/825372/-how-to-change-the-ellipsis-string-of-android-textview-to-more

And here's the link to the repo: https://github.com/TheCodeYard/EllipsizedTextView

Sheilasheilah answered 1/10, 2018 at 11:21 Comment(1)
yup i was doing this , in recycler view . it degraded the whole performance ,Tayler
A
2

Here's a nice way to do it with a Kotlin extension. Note that we need to wait for the view to layout before we can measure and append the suffix.

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
}

Then actually using it like this myTextView.setEllipsizedSuffix(2, "...See more")


Note: if your text comes from a server and may have new line characters, then you can use this method to determine if the text has ellipsized.

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 is from implementation 'org.apache.commons:commons-lang3:3.4'

Annunciata answered 23/8, 2019 at 21:25 Comment(0)
S
1

Here is a solution for Kotlin.

The yourTextView.post{} is necessary because the textview won't be ellipsized until after it is rendered.

  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)
    }
  }
Shekinah answered 4/12, 2019 at 16:26 Comment(5)
Make sure you replace the yourTextView with the text view you are trying to ellipsise.Shekinah
yes, I'm using textview. but it's working when use inside the handlerKaryosome
@Xenolion I didn't test it for multi-line, but I don't see any reason why it wouldn't work.Shekinah
The reason I think of is layout.getEllipsisStart(line) give you the ellipsis start for a particular line but not for all the text!Crease
Could be. I would say try it out and see.Shekinah
T
0

@George @jmhostalet i was doing this in my recycler view and it degraded the whole performance. `

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;
            }
        });` 
Tayler answered 16/12, 2018 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.