Ведение позиции прокрутки при добавлении в ListView с обратной бесконечной прокруткой

Я создаю чат-приложение для Android, подобное Hangouts. Для этого я использую вертикальный ListView с stackFromBottom=true и transcriptMode="normal".

Список сортируется из более старых сообщений (вверху) в младшие сообщения (внизу). В нормальном состоянии ListView прокручивается вниз, показывая самое младшее сообщение.

В ListView используется адаптер обратной бесконечной прокрутки для загрузки более (более старых) сообщений, когда пользователь прокручивается до верхней части списка. Когда старые сообщения загружаются, они добавляются в верхнюю часть списка.

Желаемое поведение заключается в том, что ListView сохраняет свою позицию прокрутки в нижней части списка, когда старые сообщения добавляются в начало. То есть, когда старые сообщения добавляются в верхнюю часть, в представлении прокрутки должны отображаться те же сообщения, которые были показаны перед добавлением старых сообщений.

К сожалению, это не работает. Вместо того, чтобы поддерживать положение прокрутки в нижней части, ListView поддерживает положение прокрутки в верхней части списка. То есть, после добавления старых сообщений в список, список показывает самые верхние (самые старые) сообщения.

Есть ли простой способ сказать ListView сохранить свою позицию прокрутки в нижней части, а не сверху, при добавлении новых элементов в начало?

Обновление

Для демонстрационных целей я максимально минимизировал прецедент и код. В следующем примере создается простой список массивов строк. При нажатии на элемент добавляется другой элемент в верхней части списка. Длинное нажатие на элемент добавит еще один элемент в нижней части списка.

Все отлично работает при добавлении элементов в нижней части списка (длинный щелчок). При добавлении элементов вверху списка (простой щелчок), есть 3 случая:

  • Если список был прокручен донизу, все работает нормально. После добавления элемента вверх, список по-прежнему прокручивается вниз.
  • Если список не прокручивается снизу (ни вверху), тогда список сохранит свою позицию прокрутки (вверху). Это заставляет список "прыгать" на один предмет вверх. Я бы хотел, чтобы этот список не "прыгал" в этом случае, я бы хотел, чтобы он сохранил свою позицию прокрутки y снизу.
  • Если список прокручивается вверху, то происходит то же самое, что и в случае 2. Предпочтительное поведение такое же, как в случае 2.

(Последние два случая на самом деле одинаковы. Я их отделил, потому что проблема может быть лучше продемонстрирована в случае 2.)

код

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

Ответ 1

Я понял это. Когда бы вы ни получали новые данные, перед изменением данных в адаптере я вызываю следующее:

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

Затем я вызываю:

adapter.setData(data);

list.setSelectionFromTop(firstVisibleItem + adapter.getCount() - oldCount + 1, pos);

Ответ 2

Сначала получите firstVisiblePosition (или последний с момента его перевернутой вниз). Я должен попробовать и проверить его самостоятельно), а затем получить верхнюю часть представления, которая добавляется в список, а затем с помощью метода setSe; ectionFromTop() вы можете прокрутить до нужного места.

код:

int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0); //or upside down (list.size - 1)
int top = (v == null) ? 0 : v.getTop(); //returns the top of the view its Y co-ordinates. [Doc][1]
mList.setSelectionFromTop(index, top);

Источник

Ответ 3

Изменить для получения дополнительной информации:

Я думаю, что нашел твою проблему. Я воспроизвел описанные варианты использования, используя отдельный список в качестве единственного представления в XML:

<ListView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</ListView>

Я исправил проблему, чтобы следить за вашими желаемыми вариантами использования, вставив макет в родительский макет:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>

</FrameLayout>

Ответ 4

после того, как вы вставьте запись в свой адаптер, вы можете попробовать использовать

   yourListView.setSelection(yourAdapter.getCount() - 1);