Хорошее решение для сохранения элементов списка, когда пользователь поворачивает телефон и сохраняет все данные в ArrayAdapter
Я использую Fragment с listView. Я заполняю ArrayAdapter, связанный с этим списком, данными, полученными в пользовательском загрузчике (из Интернета). Пользовательский ArrayAdapter поддерживает бесконечную прокрутку (paging).
Каков наилучший способ хранения элементов в ArrayAdapter, когда пользователь поворачивает устройство и сохраняет положение прокрутки в ListView?
Я собираюсь создать невизуальный фрагмент с ArrayAdapter и использовать метод setRetainInstance для сохранения значений.
Любые предложения для лучшего решения?
Ответы
Ответ 1
Чтобы работать с фреймворком Android и жизненным циклом фрагмента, вы должны реализовать метод onSaveInstanceState
в своем фрагменте. Для простоты я предположил, что у вас есть массив значений String, к которым вы можете обратиться (я обычно расширяю ArrayAdapter, чтобы инкапсулировать конструкцию представления и предоставить удобный метод для доступа ко всему базовому набору данных):
public void onSaveInstanceState(Bundle savedState) {
super.onSaveInstanceState(savedState);
// Note: getValues() is a method in your ArrayAdapter subclass
String[] values = mAdapter.getValues();
savedState.putStringArray("myKey", values);
}
Затем вы можете извлечь данные в свой метод onCreate (или onCreateView или onActivityCreated - см. фрагмент JavaDoc) следующим образом:
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
String[] values = savedInstanceState.getStringArray("myKey");
if (values != null) {
mAdapter = new MyAdapter(values);
}
}
...
}
Это гарантирует, что все события жизненного цикла будут обрабатываться надлежащим образом, без потери данных, включая поворот устройства и переход пользователя в другие приложения. Опасность использования onSaveInstanceState
и использования памяти - это опасность, что Android восстановит эту память. Сохраненное состояние не будет затронуто этим, но использование переменных экземпляра или скрытых фрагментов приведет к потере данных.
Если savedStateInstance
имеет значение null, то нет состояния для восстановления.
if (values != null)
просто защищает от возможности сохранения массива, но если вы закодируете ArrayAdapter для обработки нулевого набора данных, вам это не понадобится.
Окончательное решение, если ваши строки являются экземплярами одного из ваших собственных классов, а не отдельных элементов данных, заключается в реализации интерфейса Parcelable в этом классе, тогда вы можете использовать savedState.putParcelableArray("myKey", myArray)
. Вы были бы удивлены, насколько полезно знать, как реализовать Parcelable - он позволяет вам проходить ваши классы по сути и позволяет писать гораздо более чистый код.
Ответ 2
Когда устройство повернуто, приложение перезапускается. Поэтому onSaveInstance
вызывается до того, как приложение будет уничтожено. Вы можете сохранить адаптер массива в onSaveInstance
, а когда onCreate
наконец будет вызван, когда приложение будет запущено снова, вы можете получить адаптер массива и установить его в виде списка.
Ответ 3
Очень легко сохранить элементы вашего адаптера в onSaveInstance
.
Теперь вам нужно сохранить местоположение (Scroll). Вы можете сделать это
простой способ
Поскольку изменение screenOrientation все равно повредит, вы можете разрешить некоторую предельную ошибку и просто в своем onSaveInstance
сохранить первый видимый элемент с помощью listView.getFirstVisiblePosition()
.
Затем в onRestoreInstance
вы можете получить этот индекс для прокрутки к одному элементу с помощью listview.setSelection(position)
. Конечно, вы можете использовать listview.smoothScrollToPosition(position)
, но это было бы странно.
трудный путь
Чтобы иметь более точную позицию, вам нужно будет спуститься на еще один уровень: получите позицию прокрутки (не индекс) и сохраните эту позицию. Затем, после восстановления, прокрутите назад до этой позиции. В onSaveInstance
сохраните позицию, возвращенную с listview.getScrollY()
. Когда вы вернете это положение в onRestoreInstance
, вы можете вернуться к этой позиции с помощью listview.scrollTo(0, position)
.
Почему это действительно сложно?
Simple. Вероятно, вы, скорее всего, не сможете вернуться к этой позиции, потому что ваш список пока не пройдет измерение и макет. Вы можете преодолеть это, ожидая макета после установки адаптера с помощью getViewObserver().addOnGlobalLayoutListener
. В этом обратном вызове отправьте runnable, чтобы перейти к позиции.
Я советую использовать первый "простой способ", поскольку в целом при изменении ориентации экрана пользователь, вероятно, переместит пальцы назад. Нам просто нужно показать ему те же предметы "дайте или возьмите".
Ответ 4
когда ориентация изменяет вызовы жизненного цикла активности onPause
, а затем onRestart
Сделайте что-то вроде этого -
@Override
protected void onRestart() {
super.onRestart();
mAdapter.getFilter().filter(getFilterSettings());
mAdapter.notifyDataSetChanged();
}
Ответ 5
Я не знаю содержимое вашего ArrayAdapter
или List
, поддерживающего его, но как сделать резервную копию данных сериализуемым списком, сохранить его, а затем загрузить его при воссоздании представления?
Традиционно я видел эту оценку, используемую, когда вы пытаетесь хранить данные, когда приложение закрывается или рискует быть убитым из оставшихся в фоновом режиме. Существует большой пост сериализации в комплекте с примером кода здесь. Они проходят через ArrayList
пользовательских объектов, записывая их в файл и повторно открывая его позже. Я думаю, что если бы вы применили этот подход, записав данные вашей поддержки List
или ArrayAdapter
в onPause()
до того, как действие будет уничтожено, вы сможете перезагрузить этот файл, когда активируется активация.
Другие подходы (планы резервного копирования a/k/a):
(1) Легко, но неаккуратно. Если ваш список является некоторым примитивным, например списком строк, вы всегда можете рассмотреть возможность записи значений в SharedPreferences
и вернуть их при перезагрузке. Просто не забудьте указать уникальный идентификатор в процессе хранения.
ПРИМЕЧАНИЕ, хотя это может сработать, SharedPreferecnes
обычно не предназначен для обработки больших объемов данных, поэтому, если список длинный, я бы избегал такого подхода. Однако для нескольких точек данных я не вижу в этом проблемы.
(2) Немного сложнее, но рискованно. Если вы поддерживаете List
, адаптер содержит объекты, которые реализуют parcelable
или уже сериализуемы, рассмотрите возможность передачи List
через Intent
в существующую активность, которая в фоновом режиме и используя обратный вызов для извлечения этих данных при воссоздании активности. Это похоже на вашу идею создания фона Fragment
. Оба подхода сопряжены с риском того, что таргетинг Activity
или Fragment
не будет.
Ответ 6
Чтобы сохранить данные для просмотра списка, чтобы не перезагружать снова и снова во время отдыха фрагмента, необходимо сохранить эти данные в статическом члене класса.
Что-то вроде
class YourData{
public static List<YourDataObject> listViewData;
}
И затем из адаптера сначала загрузите данные из Интернета или из локальной базы данных
затем установите, что извлеченные данные для этого статического члена выглядят примерно так:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Run the process to get data from database
YourData.listViewData = dataRetrievedFromDatabase;
}
а затем в методе onResume обновите эти значения адаптеру примерно так:
@Override
public void onResume() {
super.onResume();
if(YourData.listViewData!=null){
yourListAdapater.setData = YourData.listViewData;
listView.setAdapter(yourListAdapater);
}else{
//run your method to get data from server
}
}
это можно использовать для сохранения любых данных за один сеанс