61

I have a TextView that I'm dynamically adding text to.

in my main.xml file I have the properties set to make my max lines 19 and scrollbars vertical.

in the .java file I am using textview.setMovementMethod(new ScrollingMovementMethod()); to allow for scrolling.

The scrolling works great. As soon as 19 lines are taken up, and more lines are added it starts scrolling just as it should. The problem is, I want the new text to scroll into view.

I am writing out the value for textview.getScrollY() and it stays at 0 no matter what (even if I manually scroll it down and add a new line of text).

consequently textview.scrollTo(0, textview.getScrollY()); does nothing for me.

Is there another method I should be using to obtain the vertical scroll amount for the textview? Everything I've read says that for all intents and purposes, what I'm doing should be working :/

15 Answers 15

83

Took some digging through the TextView source but here's what I came up with. It doesn't require you to wrap the TextView in a ScrollView and, as far as I can tell, works perfectly.

// function to append a string to a TextView as a new line // and scroll to the bottom if needed private void addMessage(String msg) { // append the new string mTextView.append(msg + "\n"); // find the amount we need to scroll. This works by // asking the TextView's internal layout for the position // of the final line and then subtracting the TextView's height final int scrollAmount = mTextView.getLayout().getLineTop(mTextView.getLineCount()) - mTextView.getHeight(); // if there is no need to scroll, scrollAmount will be <=0 if (scrollAmount > 0) mTextView.scrollTo(0, scrollAmount); else mTextView.scrollTo(0, 0); } 

Please let me know if you find a case where this fails. I'd appreciate being able to fix any bugs in my app ;)

Edit: I should mention that I also use

mTextView.setMovementMethod(new ScrollingMovementMethod()); 

after instantiating my TextView.

Sign up to request clarification or add additional context in comments.

8 Comments

This is a great suggestion, thanks! However, it doesn't work exactly as you posted, because TextView.getLayout() can return null if the text was changed recently. The solution is, like in the other answer, to put the whole scroll amount calculation and scrollTo() calls into a mTextView.post(new Runnable() { ... call.
Hi @KNfLrPn.. Can I use the scrolling logic that you mentioned for an assignment I have? Pretty please! :D
Ha, I hope you did. I'm pretty sure posting code on this site implies that we want people to find and use it.
I simply added a null check for the .getlayout. It worked fine
It's simpler to calculate the scroll amount using Layout.getHeight. Also don't forget to account for the TextView's top & bottom padding. int scrollAmount = mTextView.getLayout().getHeight - (mTextView.getHeight() + mTextView.getPaddingTop() + mTextView.getPaddingBottom());
|
62

Use android:gravity="bottom" on the TextView in your XML layout. E.g.

<TextView ... android:gravity="bottom" ... /> 

Don't ask me why it works.

The only problem with this method is if you want to then scroll back up the textview, it keeps getting "pulled down" to the bottom again each time new text is inserted.

8 Comments

For what I wanted, just information being dump continually at the bottom of a TextView, this is by far the best option. It is amazing how easy it is to miss the simple options sometimes. Thanks.
It works fine but don't show scrollable bars when it exceeds the defined size. If u also wants to scroll the view with bars sign then add android:scrollbars="vertical/horizontal" in defined xml.
I had a case where the view I wanted to scroll was the last view of a LinearLayout, and to fill the remaining space I used android:layout_height="0dp" and android:layout_weight="1", and it didn't scroll to the bottom when calling setText(). Replacing these attributes with an android:layout_height="match_parent" seems to have worked, with the TextView occupying the unused space. Weird.
Does not work for a TextView inside of a android.support.v4.widget.NestedScrollView
My problem with this approach is that it looks really odd before the TextView fills up, since what text is present all sits at the bottom. I haven't found a workaround for it, but it seems more natural for me in a "continuous logging" situation for the TextView to fill from the top at first, but then continuously scroll to the bottom once it has filled. I guess whether this bothers people will depend on how quickly and how often it fills up.
|
29

this is what I use to scroll all the way to the bottom of my chat text ...

public void onCreate(Bundle savedInstanceState) { this.chat_ScrollView = (ScrollView) this.findViewById(R.id.chat_ScrollView); this.chat_text_chat = (TextView) this.findViewById(R.id.chat_text_chat); } public void addTextToTextView() { String strTemp = "TestlineOne\nTestlineTwo\n"; //append the new text to the bottom of the TextView chat_text_chat.append(strTemp); //scroll chat all the way to the bottom of the text //HOWEVER, this won't scroll all the way down !!! //chat_ScrollView.fullScroll(View.FOCUS_DOWN); //INSTEAD, scroll all the way down with: chat_ScrollView.post(new Runnable() { public void run() { chat_ScrollView.fullScroll(View.FOCUS_DOWN); } }); } 

EDIT: here's the XML layout

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <!-- center chat display --> <ScrollView android:id="@+id/chat_ScrollView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentRight="true" android:layout_alignParentLeft="true"> <TextView android:id="@+id/chat_text_chat" android:text="center chat" android:layout_width="fill_parent" android:layout_height="fill_parent" android:singleLine="false" /> </ScrollView> </RelativeLayout> 

1 Comment

@Someone - but OP is not using a ScrollView - op is using a TextView w scrolling turned on ==> "textview.setMovementMethod(new ScrollingMovementMethod()); to allow for scrolling."
24

Previous answers did not work correctly for me, this however works.

Create a TextView and do the following:

// ... mTextView = (TextView)findViewById(R.id.your_text_view); mTextView.setMovementMethod(new ScrollingMovementMethod()); // ... 

Use the following function to append text to the TextView.

private void appendTextAndScroll(String text) { if(mTextView != null){ mTextView.append(text + "\n"); final Layout layout = mTextView.getLayout(); if(layout != null){ int scrollDelta = layout.getLineBottom(mTextView.getLineCount() - 1) - mTextView.getScrollY() - mTextView.getHeight(); if(scrollDelta > 0) mTextView.scrollBy(0, scrollDelta); } } } 

Hope this helps.

2 Comments

Really helpful. There are tone of answers but this work perfect for me
This does not seem to work if layout_width of the TextView is set to wrap_content, but otherwise is working really well for me.
11

TextView already has auto-scrolling if you set the text using Spannable or Editable strings with the cursor position set in them.

First, set the scrolling method:

mTextView.setMovementMethod(new ScrollingMovementMethod()); 

Then use the following to set the text:

SpannableString spannable = new SpannableString(string); Selection.setSelection(spannable, spannable.length()); mTextView.setText(spannable, TextView.BufferType.SPANNABLE); 

The setSelection() moves the cursor to that index. When a TextView is set to a SPANNABLE it will automatically scroll to make the cursor visible. Note that this does not draw the cursor, it just scrolls the location of the cursor to be in the viewable section of the TextView.

Also, since TextView.append() upgrades the text to TextView.BufferType.EDITABLE and Editable implements Spannable, you can do this:

mTextView.append(string); Editable editable = mTextView.getEditableText(); Selection.setSelection(editable, editable.length()); 

Here is a full widget implementation. Simply call setText() or append() on this widget. It's slightly different than above because it extends from EditText which already forces its internal text to be Editable.

import android.content.Context; import android.support.v7.widget.AppCompatEditText; import android.text.Editable; import android.text.Selection; import android.text.Spannable; import android.text.method.MovementMethod; import android.text.method.ScrollingMovementMethod; import android.text.method.Touch; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; public class AutoScrollTextView extends AppCompatEditText { public AutoScrollTextView(Context context) { this(context, null); } public AutoScrollTextView(Context context, AttributeSet attrs) { super(context, attrs); } public AutoScrollTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected boolean getDefaultEditable() { return false; } @Override protected MovementMethod getDefaultMovementMethod() { return new CursorScrollingMovementMethod(); } @Override public void setText(CharSequence text, BufferType type) { super.setText(text, type); scrollToEnd(); } @Override public void append(CharSequence text, int start, int end) { super.append(text, start, end); scrollToEnd(); } public void scrollToEnd() { Editable editable = getText(); Selection.setSelection(editable, editable.length()); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(AutoScrollTextView.class.getName()); } /** * Moves cursor when scrolled so it doesn't auto-scroll on configuration changes. */ private class CursorScrollingMovementMethod extends ScrollingMovementMethod { @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { widget.moveCursorToVisibleOffset(); return super.onTouchEvent(widget, buffer, event); } } } 

1 Comment

This is the best answer! Other solutions seems to work poorly or not all the time etc.
7

(2017) using Kotlin:

// you need this to enable scrolling: mTextView.movementMethod = ScrollingMovementMethod() // to enable horizontal scrolling, that means word wrapping off: mTextView.setHorizontallyScrolling(true) ... mTextView.text = "Some long long very long text content" mTextView.post { val scrollAmount = mTextView.layout.getLineTop(mTextView.lineCount) - mTextView.height mTextView.scrollTo(0, scrollAmount) } 

This works file for me

Comments

4
 scrollview=(ScrollView)findViewById(R.id.scrollview1); tb2.setTextSize(30); tb2=(TextView)findViewById(R.id.textView2); scrollview.fullScroll(View.FOCUS_DOWN); 

Or use this in TextView:

<TextView android:id="@+id/tb2" android:layout_width="fill_parent" android:layout_height="225sp" android:gravity="top" android:background="@android:drawable/editbox_background" android:scrollbars="vertical"/> 

2 Comments

scrollview.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub scrollview.fullScroll(View.FOCUS_DOWN); } });
@ An-droid You can set gravity_layout="center"
4

Simple implementation...in your XML layout define your TextView with these attributes:

<TextView ... android:gravity="bottom" android:scrollbars="vertical" /> 

1 Comment

This (because of the gravity setting) seems to fill up the textview from the bottom to top, so it is rarely the intended behavior.
2

I used a little trick ... in my case....

<FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@+id/textView" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_above="@+id/imageButton"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/scrollView" android:layout_gravity="left|top" > <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:inputType="textMultiLine" android:ems="10" android:text="@string/your_text" /> </ScrollView> </FrameLayout> 

Comments

1
// Layout Views private TextView mConversationView; private ScrollView mConversationViewScroller; use it either in : public void onCreate(Bundle savedInstanceState) { //...blabla setContentView(R.layout.main); //...blablabla mConversationView = (TextView) findViewById(R.id.in); mConversationViewScroller = (ScrollView) findViewById(R.id.scroller); } or in "special" method e.g. public void initializeChatOrSth(...){ //...blabla mConversationView = (TextView) findViewById(R.id.in); mConversationViewScroller = (ScrollView) findViewById(R.id.scroller); } public void addTextToTextView() { //...blablabla some code byte[] writeBuf = (byte[]) msg.obj; // construct a string from the buffer - i needed this or You can use by"stringing" String writeMessage = new String(writeBuf); mConversationView.append("\n"+"Me: " + writeMessage); mConversationViewScroller.post(new Runnable() { public void run() { mConversationViewScroller.fullScroll(View.FOCUS_DOWN); } }); } this one works fine, also we can maually scroll text to the very top - which is impossible when gravity tag in XML is used. Of course XML (main) the texview should be nested inside scrollview , e.g: <ScrollView android:id="@+id/scroller" android:layout_width="match_parent" android:layout_height="280dp" android:fillViewport="true" android:keepScreenOn="true" android:scrollbarStyle="insideInset" android:scrollbars="vertical" > <TextView android:id="@+id/in" android:layout_width="fill_parent" android:layout_height="wrap_content" android:keepScreenOn="true" android:scrollbars="vertical" > </TextView> </ScrollView> 

Comments

1

In Kotlin, This way can be useful,

First, Make the Textview scrollable:

 <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:maxLines="3" android:scrollbars="vertical" /> 

https://stackoverflow.com/a/67105173/12272687

Then, use scrollTo(x,y) to move as you need

tv.post { // Calculate the last point in Y val scrollAmount = tv.layout.getLineTop(tv.lineCount)- tv.height // From 0 to last point in Y position , where X is 0 tv.scrollTo(0, scrollAmount) } 

Comments

0

to avoid creating dummy scroolview I did

int top_scr,rec_text_scrollY; top_scr=(int)rec_text.getTextSize()+rec_text.getHeight(); rec_text_scrollY=rec_text.getLineBounds(rec_text.getLineCount()-1, null)-top_scr; //repeat scroll here and in rec_text.post. //If not scroll here text will be "jump up" after new append, and immediately scroll down //If not scroll in post, than scroll will not be actually processed if(rec_text_scrollY>0)rec_text.scrollTo(0, rec_text_scrollY); rec_text.post(new Runnable(){ @Override public void run() { if(rec_text_scrollY>0)rec_text.scrollTo(0, rec_text_scrollY); } }); 

Comments

0

https://stackoverflow.com/a/7350267/4411645 didn't exactly work for me for

  1. the getLayout can throw NPE when the text is recently changed.
  2. scrollTo should be changed to scrollBy
  3. It doesn't take relative position of the textview or paddings into account.

Needless to say it is a good starting point. Here's my variation of the implementation, within a textwatcher. We have to subtract the textView top from the bottom when calculating the delta because getLineTop() also returns the value relative to the textView top.

 @Override public void afterTextChanged(Editable editable) { new Handler().postDelayed(new Runnable() { @Override public void run() { Layout layout = textView.getLayout(); if (layout != null) { int lineTop = layout.getLineTop(textView.getLineCount()); final int scrollAmount = lineTop + textView.getPaddingTop() + textView.getPaddingBottom() - textView.getBottom() + textView.getTop(); if (scrollAmount > 0) { textView.scrollBy(0, scrollAmount); } else { textView.scrollTo(0, 0); } } } }, 1000L); } 

You can play around with the delay to better your ux.

Comments

0

Based on the answer from KNfLrPn , and correcting some issues from that answer, there is a solution that is still valid in Android Studio 3.4.2 in 2019 and that I've tested in my developing app.

 private void addMessage(String msg) { mTextView.append(msg + "\n"); final int scrollAmount = max(mTextView.getLayout().getLineBottom( mTextView.getLineCount()-1) - mTextView.getHeight(),0); mTextView.post(new Runnable() { public void run() { mTextView.scrollTo(0, mScrollAmount + mTextView.getLineHeight()/3); }}); mTextView.scrollTo(0, scrollAmount); } 

There were some problems, some of them pointed out in the comments and other answers:

a) The line mTextView.getLayout().getLineTop(mTextView.getLineCount()) gives a bound error. The equivalent to mTextView.getLayout().getLineTop(L) is mTextView.getLayout().getLineBottom(L-1). So I've replaced it by the line

mTextView.getLayout().getLineBottom( mTextView.getLineCount()-1) - mTextView.getHeight() 

b) The max is just for simplify the logic

c) scrollTo should appear within a post method in a kind of thread.

d) plain vanilla last line bottom doesn't solve the problem completely apparently because there is a bug that the last line of TextView that should appear completely in the view, appears cut off. So I add about 1/3 of the height of the line to the scroll. This can be calibrated, but it has worked well for me.

-/-

Sometimes what seems obvious needs to be said: The value of x and y corresponds to the scrollTo routine exactly matches the number of pixels in the text that is invisible on the left (x) and the number of pixels in the text that is invisible on the top (y). This corresponds exactly to the value of the widgets scrollX and scrollY properties.

Thus, when one takes the y from the last line of the text, and if this is a value greater than the widget's height, it must correspond exactly to the number of pixels of the text that needs to be hidden, which is entered as a parameter of the scrollTo method.

The third of the height of the line that I've added, put the scroll a little higher, making the last line fully visible, precisely because of practice does not correspond exactly to what the theory advocates.

Comments

0

The cleanest solution if you want to automatically scroll to the bottom every time you add text to the TextView is to add the following code:

class ScrollToEndMovementMethod(private val widget: TextView) : ScrollingMovementMethod() { fun scrollToBottom(): Boolean { return bottom(widget, null) } } 

.....

// in your code: textView = .... val scroller= ScrollToEndMovementMethod(textView) ..... textView.text="your text" scroller.scrollToBottom() 

Unfortunately, simply adding the line android:gravity="bottom" in the XML file is not enough.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.