73

I want to get the number of lines of a text view

textView.setText("Test line 1 Test line 2 Test line 3 Test line 4 Test line 5.............")

textView.getLineCount(); always returns zero

Then I have also tried:

ViewTreeObserver vto = this.textView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        ViewTreeObserver obs = textView.getViewTreeObserver();
        obs.removeGlobalOnLayoutListener(this);
        System.out.println(": " + textView.getLineCount());

    }
});

It returns the exact output.

But this works only for a static layout.

When I am inflating the layout dynamically this doesn't work anymore.

How could I find the number of line in a TextView?

Toon Krijthe
  • 52,876
  • 38
  • 145
  • 202
MAC
  • 15,799
  • 8
  • 54
  • 95
  • yes i know, i have already mentioned that not i face this problem while inflating layout dynamically. – MAC Aug 20 '12 at 12:16
  • I think [this is helpful for you.][1] [1]: http://stackoverflow.com/questions/2239356/is-there-a-way-of-retrieving-a-textviews-visible-line-count-or-range – Hardik Joshi Aug 20 '12 at 12:18
  • @hardikjoshi:thax already tried this, but didn't work. – MAC Aug 20 '12 at 15:05
  • Possible duplicate of [Is there a way of retrieving a TextView's visible line count or range?](http://stackoverflow.com/questions/2239356/is-there-a-way-of-retrieving-a-textviews-visible-line-count-or-range) – Raut Darpan Apr 11 '17 at 13:30
  • See also https://stackoverflow.com/questions/15679147/how-to-get-line-count-of-textview-before-rendering/28525249. – CoolMind Nov 09 '20 at 11:16

10 Answers10

113

I was able to get getLineCount() to not return 0 using a post, like this:

textview.setText(“Some text”);
textview.post(new Runnable() {
    @Override
    public void run() {
        int lineCount = textview.getLineCount();
        // Use lineCount here
    }
});
Cactus
  • 27,075
  • 9
  • 69
  • 149
Marilia
  • 1,911
  • 1
  • 19
  • 28
  • Could you give more details about your code that was trying to get the line count of a `TextView`? Maybe there's some detail in the adapter that may be causing the views to be loaded differently than the expected? – Marilia Jun 26 '17 at 18:21
  • I solved it by different approach and now its working fine I followed this answer https://stackoverflow.com/a/22217849/2579281 – Vikash Kumar Verma Jun 26 '17 at 18:41
  • Thanks for sharing what worked for your `RecycleView`. That's an interesting approach and definitely worth a try if this simpler code didn't do the trick for that specific case. :) – Marilia Jun 26 '17 at 19:06
42

As mentioned in this post,

getLineCount() will give you the correct number of lines only after a layout pass.

It means that you need to render the TextView first before invoking the getLineCount() method.

Community
  • 1
  • 1
aymeric
  • 3,877
  • 2
  • 28
  • 42
  • 3
    thax for ans but still have problem – MAC Aug 20 '12 at 15:05
  • 2
    this is correct. You must not be adding the listener correctly. Add the OnGlobalLayoutListener directly to the TextView instance and not the parent layout. – user123321 Dec 27 '12 at 20:43
  • 1
    This only works if textview contains line breaks, but it doesn't work for word wrap –  May 21 '17 at 22:01
33

ViewTreeObserver is not so reliable especially when using dynamic layouts such as ListView.

Let's assume:
1. You will do some work depending on the lines of TextView.
2. The work is not very urgent and can be done later.

Here is my solution:

public class LayoutedTextView extends TextView {

        public LayoutedTextView(Context context) {
                super(context);
        }

        public LayoutedTextView(Context context, AttributeSet attrs) {
                super(context, attrs);
        }

        public LayoutedTextView(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
        }

        public interface OnLayoutListener {
                void onLayouted(TextView view);
        }

        private OnLayoutListener mOnLayoutListener;

        public void setOnLayoutListener(OnLayoutListener listener) {
                mOnLayoutListener = listener;
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right,
                        int bottom) {
                super.onLayout(changed, left, top, right, bottom);

                if (mOnLayoutListener != null) {
                        mOnLayoutListener.onLayouted(this);
                }
        }

}

Usage:

LayoutedTextView tv = new LayoutedTextView(context);
tv.setOnLayoutListener(new OnLayoutListener() {
        @Override
        public void onLayouted(TextView view) {
                int lineCount = view.getLineCount();
                // do your work
        }
});
ישו אוהב אותך
  • 28,609
  • 11
  • 78
  • 96
secnelis
  • 331
  • 3
  • 2
23
 textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

        @Override
        public boolean onPreDraw() {
            // Remove listener because we don't want this called before _every_ frame
            textView.getViewTreeObserver().removeOnPreDrawListener(this)

            // Drawing happens after layout so we can assume getLineCount() returns the correct value
            if(textView.getLineCount() > 2) {
                // Do whatever you want in case text view has more than 2 lines
            }  

            return true; // true because we don't want to skip this frame
        }
});
Zsolt Safrany
  • 13,290
  • 6
  • 50
  • 62
priti
  • 892
  • 9
  • 9
  • 3
    Not sure why this didn't get more vote. This needs to the selected answer. Using a layout listener will provide callback once the layout is complete, which means that any changes that are needs in the purview of the parent are not possible. Example: a scroll view having children. How does one make all the children adjust depending on something happening in one of the children. Thus, OnPreDraw works better and more efficiently. – Sunny Nov 10 '18 at 09:49
  • inside this method getting infinite number of calls – Muhammed Haris Feb 19 '20 at 10:42
  • using RecyclerView.Adapter call every time and hang phone @priti – Kishan Viramgama Mar 24 '20 at 11:21
10

I think the crux of this question is that people want to be able to find out the size of a TextView in advance so that they can dynamically resize it to nicely fit the text. A typical use might be to create talk bubbles (at least that was what I was working on).

I tried several solutions, including use of getTextBounds() and measureText() as discussed here. Unfortunately, both methods are slightly inexact and have no way to account for line breaks and unused linespace. So, I gave up on that approach.

That leaves getLineCount(), whose problem is that you have to "render" the text before getLineCount() will give you the number of lines, which makes it a chicken-and-egg situation. I read various solutions involving listeners and layouts, but just couldn't believe that there wasn't something simpler.

After fiddling for two days, I finally found what I was looking for (at least it works for me). It all comes down to what it means to "render" the text. It doesn't mean that the text has to appear onscreen, only that it has to be prepared for display internally. This happens whenever a call is made directly to invalidate() or indirectly as when you do a setText() on your TextView, which calls invalidate() for you since the view has changed appearance.

Anyway, here's the key code (assume you already know the talk bubble's lineWidth and lineHeight of a single line based on the font):

TextView talkBubble;
// No peeking while we set the bubble up.
talkBubble.setVisibility( View.INVISIBLE );
// I use FrameLayouts so my talk bubbles can overlap
// lineHeight is just a filler at this point
talkBubble.setLayoutParams( new FrameLayout.LayoutParams( lineWidth, lineHeight ) );
// setText() calls invalidate(), which makes getLineCount() do the right thing.
talkBubble.setText( "This is the string we want to dynamically deal with." );
int lineCount = getLineCount();
// Now we set the real size of the talkBubble.
talkBubble.setLayoutParams( new FrameLayout.LayoutParams( lineWidth, lineCount * lineHeight ) );
talkBubble.setVisibility( View.VISIBLE );

Anyway, that's it. The next redraw will give a bubble tailor-made for your text.

Note: In the actual program, I use a separate bubble for determining lines of text so that I can resize my real bubble dynamically both in terms of length and width. This allows me to shrink my bubbles left-to-right for short statements, etc.

Enjoy!

Community
  • 1
  • 1
Weeman
  • 151
  • 1
  • 5
  • 5
    I'd guess that this works for you because you are using a fixed line width in code, or a pre-calculated one. For wrap content or match parent, this doesn't work, though, as those values also still have to be calculated. – lilbyrdie Sep 15 '13 at 20:58
9

You could also use PrecomputedTextCompat for getting the number of lines. Regular method:

fun getTextLineCount(textView: TextView, text: String, lineCount: (Int) -> (Unit)) {
    val params: PrecomputedTextCompat.Params = TextViewCompat.getTextMetricsParams(textView)
    val ref: WeakReference<TextView>? = WeakReference(textView)

    GlobalScope.launch(Dispatchers.Default) {
        val text = PrecomputedTextCompat.create(text, params)
        GlobalScope.launch(Dispatchers.Main) {
            ref?.get()?.let { textView ->
                TextViewCompat.setPrecomputedText(textView, text)
                lineCount.invoke(textView.lineCount)
            }
        }
    }
}

Call this method:

getTextLineCount(textView, "Test line 1 Test line 2 Test line 3 Test line 4 Test line 5.............") { lineCount ->
     //count of lines is stored in lineCount variable
}

Or maybe you can create extension method for it like this:

fun TextView.getTextLineCount(text: String, lineCount: (Int) -> (Unit)) {
    val params: PrecomputedTextCompat.Params = TextViewCompat.getTextMetricsParams(this)
    val ref: WeakReference<TextView>? = WeakReference(this)

    GlobalScope.launch(Dispatchers.Default) {
        val text = PrecomputedTextCompat.create(text, params)
        GlobalScope.launch(Dispatchers.Main) {
            ref?.get()?.let { textView ->
                TextViewCompat.setPrecomputedText(textView, text)
                lineCount.invoke(textView.lineCount)
            }
        }
    }
}

and then you call it like this:

textView.getTextLineCount("Test line 1 Test line 2 Test line 3 Test line 4 Test line 5.............") { lineCount ->
     //count of lines is stored in lineCount variable
}
BVantur
  • 1,182
  • 16
  • 11
8

Based on @secnelis idea, there is even a more clean way if you target API 11 or higher.

Instead of extending a TextView you can use already built-in functionality if View.OnLayoutChangeListener

In ListAdapter.getView(), for instance

if (h.mTitle.getLineCount() == 0 && h.mTitle.getText().length() != 0) {
    h.mTitle.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(final View v, final int left, final int top,
                final int right, final int bottom, final int oldLeft,
                final int oldTop, final int oldRight, final int oldBottom) {
            h.mTitle.removeOnLayoutChangeListener(this);
            final int count = h.mTitle.getLineCount();
            // do stuff
        }
    });
} else {
    final int count = h.mTitle.getLineCount();
    // do stuff
}
Community
  • 1
  • 1
Yaroslav Mytkalyk
  • 16,950
  • 10
  • 72
  • 99
3

You can also calculate the amount of lines through this function:

private fun countLines(textView: TextView): Int {
        return Math.ceil(textView.paint.measureText(textView.text.toString()) /
                textView.measuredWidth.toDouble()).toInt()
    }

Keep in mind that It may not work very well on a RecyclerView though.

4gus71n
  • 3,717
  • 3
  • 39
  • 66
1
textview.getText().toString().split(System.getProperty("line.separator")).length

It works fine for me to get number of lines of TextView.

Mehedi Hasan Shagor
  • 750
  • 1
  • 8
  • 14
0

Are you doing this onCreate? The Views aren't laid out yet, so getLineCount() is 0 for a while. If you do this later in the Window LifeCycle, you'll get your line count. You'll have a hard time doing it onCreate, but onWindowFocusChanged with hasFocus=true usually has the Views measured by now.

The textView.post() suggestion is also a good one

Cactus
  • 27,075
  • 9
  • 69
  • 149
Joe Plante
  • 6,308
  • 2
  • 30
  • 23