Сохранять позицию прокрутки при добавлении в ListView с обратной бесконечной прокруткой

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

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

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

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

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

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

Обновить

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

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

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

4 ответа

Решение

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

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

Сначала получите firstVisiblePosition (или Last с тех пор, как оно перевернулось. Вы должны попытаться проверить это самостоятельно), а затем получите верхнюю часть представления, которая добавлена ​​в список, а затем с помощью метода 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);

Источник.

Перейдите в аккаунт для получения дополнительной информации:

Я думаю, что нашел вашу проблему. Я повторил описанные варианты использования, используя автономный список в качестве единственного представления в 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>

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

   yourListView.setSelection(yourAdapter.getCount() - 1);
Другие вопросы по тегам