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 scrollbar
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.
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);
}
}
}
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.
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.
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>
// 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>