开发者

auto-scrolling TextView in android to bring text into view

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 开发者_JS百科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 :/


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.


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.


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>


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.


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);
        }
    }
}


(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


 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"/>


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

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


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>


// 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>


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);
    }                   
});


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜