96

I have a multi-line TextView that has android:ellipsize="end" set. I would like to know, however, if the string I place in there is actually too long (so that I may make sure the full string is shown elsewhere on the page).

I could use TextView.length() and find about what the approximate length of string will fit, but since it's multiple lines, the TextView handles when to wrap, so this won't always work.

Any ideas?

ByteHamster
  • 4,884
  • 9
  • 38
  • 53
FrederickCook
  • 3,018
  • 4
  • 24
  • 26

19 Answers19

140

You can get the layout of the TextView and check the ellipsis count per line. For an end ellipsis, it is sufficient to check the last line, like this:

Layout l = textview.getLayout();
if (l != null) {
    int lines = l.getLineCount();
    if (lines > 0)
        if (l.getEllipsisCount(lines-1) > 0)
            Log.d(TAG, "Text is ellipsized");
}

This only works after the layout phase, otherwise the returned layout will be null, so call this at an appropriate place in your code.

ByteHamster
  • 4,884
  • 9
  • 38
  • 53
Thorstenvv
  • 5,376
  • 1
  • 34
  • 28
  • 8
    This fails if you enter a long text with no spaces on the last line of your input, something like "jashdkjashdkjashdkasjhdashdkasjdhkajsdhkasjdhakjshdksjahdaksjhdajkshdkajshdkjashdkjashdja". getEllipsisCount() will return 0 in that case. To fix it, instead of only checking the last line, you must loop through all the lines and check if ( l.getEllipsisCount(n) > 0 ) return true; for all 0 <= n < lines. – Bitcoin Cash - ADA enthusiast Feb 05 '15 at 02:21
  • 1
    Because of bug in `getEllipsisCount` function in API <= 14, it will fail and return 0 if the text contains special chars like '\n'. So you can workaround it by using the support libraries and replacing `if (l.getEllipsisCount(lines-1) > 0)` with `if (l.getEllipsisCount(lines-1) > 0 || TextViewCompat.getMaxLines(this))`. – Bam May 19 '17 at 10:10
  • 3
    Ugh, use brackets even for single line statement ifs please – r3flss ExlUtr Nov 24 '17 at 20:03
  • 1
    @r3flssExlUtr, and that is why exactly? – zarsky Nov 06 '18 at 14:51
  • Could you please elaborate what what you mean by the layout phase. Do you mean during when the layout is inflated and why would the become null later? – 6rchid May 31 '19 at 20:18
  • @NocTurn The TextView has to have performed its internal text layout already. This usually happens during layout. A safe bet is to wset a listener for `onGlobalLayout`(see Himanshu Virmani's answer) or `onPreDraw`. The layout could become null later when there are changes made to the TextView such as a new text being set, which will require a new internal text layout to be generated. Note that this has nothing to do with layout inflation (`android.text.Layout` vs. the concept of "Layout" as a view hierarchy) – Thorstenvv Jun 05 '19 at 10:17
  • 1. given error **classifier layout does not have a companion object** when using `Layout` type **In Kotlin**. 2. `getEllipsisCount` function returned **0**. please help me – NIKUNJ GADHIYA Jan 01 '20 at 04:44
39

textView.getLayout is the way to go but the problem with that is that it returns null if layout is not prepared. Use the below solution.

 ViewTreeObserver vto = textview.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
           Layout l = textview.getLayout();
           if ( l != null){
              int lines = l.getLineCount();
              if ( lines > 0)
                  if ( l.getEllipsisCount(lines-1) > 0)
                    Log.d(TAG, "Text is ellipsized");
           }  
        }
    });

Code snippet for removing the listener (source):

mLayout.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    public void onGlobalLayout() {
        scrollToGridPos(getCenterPoint(), false);
        mLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);        
    }
});
Alon
  • 883
  • 1
  • 6
  • 18
Himanshu Virmani
  • 2,450
  • 1
  • 24
  • 34
  • 17
    Don't forget to `removeOnGlobalLayoutListener()` when you don't need it anymore. – mmathieum Dec 15 '13 at 23:19
  • 1
    Add this when the ellipsize is found: if (Build.VERSION.SDK_INT < 16) { textview.getViewTreeObserver().removeGlobalOnLayoutListener(this); } else{ textview.getViewTreeObserver().removeOnGlobalLayoutListener(this); } – Daniel Wilson Apr 09 '14 at 15:15
  • 18
    Use view.post() rather than VTO! It's much more simple. – a person Apr 28 '15 at 19:41
  • Even more ugh, use brackets even for single line statement ifs – r3flss ExlUtr Nov 24 '17 at 20:03
  • 1. given error **classifier layout does not have a companion object** when using `Layout` type **In Kotlin**. 2. `getEllipsisCount` function returned **0**. please help me – NIKUNJ GADHIYA Jan 01 '20 at 04:30
38

I think the easiest solution to this question is the following code:

String text = "some looooong text";
textView.setText(text);
boolean isEllipsize = !((textView.getLayout().getText().toString()).equalsIgnoreCase(text));

This code assumes that in your XML the TextView set a maxLineCount :)

Francesco Florio
  • 1,184
  • 14
  • 16
  • 3
    lazy but effective :-D – msamardzic Jul 22 '16 at 01:27
  • 4
    Your test will fails if any sort capitalization is set. Add toLowerCase() calls to both strings. – Yarh Apr 28 '17 at 14:33
  • 1
    If your textView.getLayout() returns null then you should use: final ViewTreeObserver vto = tv.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { tv.getViewTreeObserver().removeOnGlobalLayoutListener(this); Layout layout = tv.getLayout(); if(!((layout.getText().toString()).equalsIgnoreCase(text))){ // ellipsized }else{ // NOT ellipsized }}}); – Royz Oct 03 '18 at 14:19
  • 6
    Need to do it after textView have render the text. textView.post(() -> {//do it here}) – Sarith Nob Mar 11 '19 at 09:17
  • 1
    Strange that the length of both objects is the same yet they are different to what is visible. – Sufian Apr 30 '19 at 16:29
  • @SarithNOB 's suggestion made this solution work for me. – Michael Osofsky Aug 16 '19 at 22:28
  • I've found this is a good solution if you have singleLine="true" in your xml – jj. Aug 26 '20 at 17:05
9

This worked to me:

textView.post(new Runnable() {
                        @Override
                        public void run() {
                            if (textView.getLineCount() > 1) {
                                //do something
                            }
                        }
                    });
Cícero Moura
  • 2,027
  • 1
  • 24
  • 36
9

The most eloquent solution I have found (in Kotlin) is to create an extension function on TextView

fun TextView.isEllipsized() = layout.text.toString() != text.toString()

This is great because it doesn't require knowing what the full string is or worrying about how many lines the TextView is using.

TextView.text is the full text that it's trying to show, whereas TextView.layout.text is what's actually shown on the screen so if they are different it must be getting ellipsized

To use it:

if (my_text_view.isEllipsized()) {
    ...
}
Matt Smith
  • 836
  • 10
  • 21
  • Get NPE on layout. Can you please tell me a solution? – Nimisha V Oct 21 '21 at 04:46
  • @NimishaV I would guess your calling `isEllipsized` before the layout is complete or after the textview is removed. See this question: https://stackoverflow.com/questions/16558948/how-to-use-textview-getlayout-it-returns-null I suggest opening a new question with the code that reproduces the error if you want help with it. – Matt Smith Oct 21 '21 at 21:32
  • I got a solution for this error. – Nimisha V Oct 22 '21 at 05:40
  • Don't, like I did, try to be smart and use `text.length` in this case, because it does not work. Numbers are always the same. – Primož Ivančič Jul 29 '22 at 12:54
  • this solution worked for me, in my opinion this solution is good – Abror Esonaliev Aug 03 '22 at 21:05
6

public int getEllipsisCount (int line):

Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place.

So, simply call :

int lineCount = textview1.getLineCount();

if(textview1.getLayout().getEllipsisCount(lineCount) > 0) {
   // Do anything here..
}

Since the getLayout() cant be called before the layout is set, use this:

ViewTreeObserver vto = textview.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
       Layout l = textview.getLayout();
       if ( l != null){
          int lines = l.getLineCount();
          if ( lines > 0)
              if ( l.getEllipsisCount(lines-1) > 0)
                Log.d(TAG, "Text is ellipsized");
       }  
    }
});

And finally do not forget to remove removeOnGlobalLayoutListener when you need it nomore.

chandramohan
  • 558
  • 5
  • 19
amalBit
  • 12,041
  • 6
  • 77
  • 94
  • You are free to edit the answer with the correct one. @FrancescoDonzello – amalBit Oct 29 '14 at 09:48
  • Might the code not being superb but the approach is it for sure. This solved my problem on determining if the text has been elippsized. This approach is very useful when you do add views to the window through the window manager. – Yoraco Gonzales Jan 28 '15 at 23:01
  • Sadly, it seems like if the ellipsis is occurring right in an empty line ("\n") it is returning 0 too, which is correct since it is happening in the 0 index character. So in that case it doesn't seem to be a way to tell... – Shyri Sep 06 '17 at 14:13
3
lateinit var toggleMoreButton: Runnable
toggleMoreButton = Runnable {
  if(reviewTextView.layout == null) { // wait while layout become available
       reviewTextView.post(toggleMoreButton) 
       return@Runnable
  }
  readMoreButton.visibility = if(reviewTextView.layout.text.toString() != comment) View.VISIBLE else View.GONE
}
reviewTextView.post(toggleMoreButton)

It is some typical case:

  1. comment in 'reviewTextView'
  2. comment can collapsed by some criteria
  3. if comment collapsed you show button 'readMoreButton'
A.Y.
  • 389
  • 3
  • 5
2

The Kotlin way:

textView.post {
   if (textView.lineCount > MAX_LINES_COLLAPSED) {
   // text is not fully displayed
   }
}

Actually View.post() is executed after the view has been rendered and will run the function provided

4ndro1d
  • 2,926
  • 7
  • 35
  • 65
2

Simple Kotlin method. Allows android:ellipsize and android:maxLines to be used

fun isEllipsized(textView: TextView, text: String?) = textView.layout.text.toString() != text
Chaseos
  • 41
  • 3
  • Hello, I understand you have converted it to kotlin, but there seems to already be an answer doing exactly the same thing you did in java. Next time, maybe you can edit the existing answer or maybe suggest an edit in the comment to the answer instead of adding a entirely new answer. Happy coding !! – because_im_batman Sep 01 '20 at 19:08
2

Solution with kotlin extensions:

infoText.afterLayoutConfiguration {
    val hasEllipsize = infoText.hasEllipsize()
    ...
}

Extensions:

/**
 * Function for detect when layout completely configure.
 */
fun View.afterLayoutConfiguration(func: () -> Unit) {
    viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            viewTreeObserver?.removeOnGlobalLayoutListener(this)
            func()
        }
    })
}

fun TextView.hasEllipsize(): Boolean = layout.getEllipsisCount(lineCount - 1) > 0
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
1

it is working for me

if (l != null) {
    int lines = l.getLineCount();
     if (lines > 0) {
     for (int i = 0; i < lines; i++) {
     if (l.getEllipsisCount(i) > 0) {
      ellipsize = true;
      break;
     }
    }
   }
  }
coffee
  • 81
  • 3
1

If your textview contains multiple paragraphs, using getEllipsisCount will not work for empty lines within it. getEllipsisCount for the last line of any paragraph will return 0.

1

After researching I found the best way for me in Kotlin

To get the ellipsize status the textView must be rendered first, so we have to set the text first, then check the ellipsize logic inside textView.post scope

textView.text = "your text"
textView.post {
    var ellipsized: Boolean = textView.layout.text.toString()).equalsIgnoreCase("your text"))

    if(ellipsized){
        //your logic goes below
      
    }
}
Md. Rejaul Karim
  • 694
  • 7
  • 14
0

Really work so, for example, to pass full data to dialog from item of RecyclerView:

holder.subInfo.post(new Runnable() {
                @Override
                public void run() {
                    Layout l = holder.subInfo.getLayout();
                    if (l != null) {
                        final int count = l.getLineCount();
                        if (count >= 3) {
                            holder.subInfo.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View view) {
                                    final int c = holder.subInfo.getLineCount();
                                    if (c >= 3) {
                                        onClickToShowInfoDialog.showDialog(holder.title.getText().toString(), holder.subInfo.getText().toString());
                                    }
                                }
                            });
                        }
                    }
                }
            });
nicolas asinovich
  • 3,201
  • 3
  • 27
  • 37
0

Combining @Thorstenvv awnser with @Tiano fix, here is the Kotlin version :

val layout = textView.layout ?: return@doOnLayout
val lines = layout.lineCount
val hasLine = lines > 0
val hasEllipsis = ((lines - 1) downTo 0).any { layout.getEllipsisCount(it) > 0 }
if (hasLine && hasEllipsis) {
    // Text is ellipsized
}
Victor
  • 197
  • 1
  • 4
0

In Kotlin, you can use the below code.

var str= "Kotlin is one of the best languages."
textView.text=str
textView.post {
val isEllipsize: Boolean = !textView.layout.text.toString().equals(str)

if (isEllipsize) {
     holder.itemView.tv_viewMore.visibility = View.VISIBLE
} else {
     holder.itemView.tv_viewMore.visibility = View.GONE
}    
}
hetsgandhi
  • 726
  • 3
  • 9
  • 25
0

This is simple library for creating textview expandable. Like Continue or Less. This library extended version TextView. Easy to use.

implementation 'com.github.mahimrocky:ShowMoreText:1.0.2'

Like this, 1 https://raw.githubusercontent.com/mahimrocky/ShowMoreText/master/screenshot1.png

2 https://raw.githubusercontent.com/mahimrocky/ShowMoreText/master/screenshot2.png

 <com.skyhope.showmoretextview.ShowMoreTextView
    android:id="@+id/text_view_show_more"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/text"
    />

In Activity you can use like:

ShowMoreTextView textView = findViewById(R.id.text_view_show_more);

//You have to use following one of method    

// For using character length
textView.setShowingChar(numberOfCharacter);
//number of line you want to short
textView.setShowingLine(numberOfLine);
-1

Using getEllipsisCount won't work with text that has empty lines within it. I used the following code to make it work :

message.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {

            if(m.isEllipsized == -1) {
                Layout l = message.getLayout();
                if (message.getLineCount() > 5) {
                    m.isEllipsized = 1;
                    message.setMaxLines(5);
                    return false;
                } else {
                    m.isEllipsized = 0;
                }
            }
            return true;
        }
    });

Make sure not to set a maxLineCount in your XML. Then you can check for the lineCount in your code and if it is greater than a certain number, you can return false to cancel the drawing of the TextView and set the line count as well as a flag to save whether the text view is too long or not. The text view will draw again with the correct line count and you will know whether its ellipsized or not with the flag.

You can then use the isEllipsized flag to do whatever you require.

skuntsel
  • 11,624
  • 11
  • 44
  • 67
js123
  • 96
  • 2
  • 8
-1

create a method inside your TextViewUtils class

public static boolean isEllipsized(String newValue, String oldValue) {
    return !((newValue).equals(oldValue));
}

call this method when it's required eg:

        if (TextViewUtils.isEllipsized(textviewDescription.getLayout().getText().toString(), yourModelObject.getDescription()))
            holder.viewMore.setVisibility(View.VISIBLE);//show view more option
        else
            holder.viewMore.setVisibility(View.GONE);//hide 

but textView.getLayout() can't call before the view(layout) set.

Abdul Rizwan
  • 3,904
  • 32
  • 31