Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maintain scroll position when adding to ListView with reverse endless-scrolling

I am building a chat-like Android application, similar to Hangouts. For this purpose I am using a vertical ListView with stackFromBottom=true and transcriptMode="normal".

The list is sorted from older messages (top) to younger messages (bottom). In normal state, the ListView is scrolled to the bottom, showing the youngest message.

The ListView uses a reverse endless-scroll adapter to load more (older) messages once the user scrolls to the top of the list. Once older messages are loaded, they are added to the top of the list.

The desired behavior is that the ListView maintains its scroll-position to the bottom of the list, when older messages are added to the top of it. That is, when older messages are added to the top, the scroll-view should show the same messages that were shown before adding the older messages.

Unfortunately, this does not work. Instead of maintaining the scroll-position to the bottom, the ListView maintains the scroll-position to the top of the list. That is, after adding older messages to the list, the list shows the top-most (i.e oldest) messages.

Is there an easy way to tell the ListView to maintain its scroll-position to the bottom, instead of the top, when adding new items to the top of it?

Update

For demonstration purposes, I have minimized the use-case and code as much as possible. The following example creates a simple string array list. Clicking on an item will add another item at the top of the list. Long-Clicking on an item will add another item at the bottom of the list.

Everything works fine when adding items at the bottom of the list (long-click). When adding items at the top of the list (simple click), then there are 3 cases:

  • If the list was scrolled to the bottom, then everything works fine. After adding the item to the top, the list is still scrolled to the bottom.
  • If the list is not scrolled to the bottom (nor to the top), then the list will maintain its scroll-y position (from top). This makes the list appear to "jump" one item up. I would like the list not to "jump up" in this case, i.e I would like it to maintain its scroll-y position from the bottom.
  • If the list is scrolled to the top, then the same happens as in case 2. The preferred behavior is the same as case 2.

(The last 2 cases are in fact the same. I separated them because the problem can be better demonstrated in case 2.)

Code

public class MyListActivity extends Activity {
  int topIndex = 1;

  int bottomIndex = 20;

  protected final void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.my_list_activity);

    final ListView list = (ListView) findViewById(R.id.list);
    list.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
    list.setStackFromBottom(true);

    final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1);
    for (int i = topIndex; i <= bottomIndex; i++) {
      adapter.add(Integer.toString(i));
    }
    list.setAdapter(adapter);

    list.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        adapter.insert(Integer.toString(--topIndex), 0);
      }
    });

    list.setOnItemLongClickListener(new OnItemLongClickListener() {
      @Override
      public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        adapter.add(Integer.toString(++bottomIndex));
        return true;
      }
    });
  }
}
like image 628
Fatih Coşkun Avatar asked Feb 26 '14 19:02

Fatih Coşkun


1 Answers

I have figured this out. Wherever it is that you are getting new data I call the following before changing data in the adapter:

int firstVisibleItem = list.getFirstVisiblePosition();
int oldCount = adapter.getCount();
View view = list.getChildAt(0);
int pos = (view == null ? 0 :  view.getBottom());

Then I call:

adapter.setData(data);

list.setSelectionFromTop(firstVisibleItem + adapter.getCount() - oldCount + 1, pos);
like image 101
Leo Avatar answered Sep 29 '22 11:09

Leo