У меня есть длинный ListView
, который пользователь может прокрутить, прежде чем вернуться к предыдущему экрану. Когда пользователь снова откроет этот ListView
, я хочу, чтобы список был прокручен до той же точки, что и раньше. Любые идеи о том, как достичь этого?
Сохранять/сохранять/восстанавливать позицию прокрутки при возврате в ListView
Ответ 1
Попробуйте следующее:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.setSelectionFromTop(index, top);
Объяснение: Аренда на отпуск
ListView.getFirstVisiblePosition()
возвращает элемент верхнего видимого списка. Но этот элемент может быть частично прокручен вне поля зрения, и если вы хотите восстановить точную позицию прокрутки списка, вам нужно получить это смещение. Поэтому ListView.getChildAt(0)
возвращает View
для элемента верхнего списка, а затем View.getTop() - mList.getPaddingTop()
возвращает относительное смещение от вершины ListView
. Затем, чтобы восстановить положение прокрутки ListView
, мы вызываем ListView.setSelectionFromTop()
с индексом нужного элемента и смещением, чтобы расположить его верхний край от вершины ListView
.
Ответ 2
Parcelable state;
@Override
public void onPause() {
// Save ListView state @ onPause
Log.d(TAG, "saving listview state @ onPause");
state = listView.onSaveInstanceState();
super.onPause();
}
...
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Set new items
listView.setAdapter(adapter);
...
// Restore previous state (including selected item index and scroll position)
if(state != null) {
Log.d(TAG, "trying to restore listview state..");
listView.onRestoreInstanceState(state);
}
}
Ответ 3
Я принял решение, предложенное @(Kirk Woll), и оно работает для меня. Я также видел в исходном коде Android для приложения "Контакты", что они используют подобную технику. Я хотел бы добавить несколько деталей: В верхней части моего класса, полученного от ListActivity:
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;
Затем некоторый метод переопределяет:
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
mListState = state.getParcelable(LIST_STATE);
}
@Override
protected void onResume() {
super.onResume();
loadData();
if (mListState != null)
getListView().onRestoreInstanceState(mListState);
mListState = null;
}
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
mListState = getListView().onSaveInstanceState();
state.putParcelable(LIST_STATE, mListState);
}
Конечно, "loadData" - это моя функция для извлечения данных из БД и их размещения в списке.
На моем устройстве Froyo это работает как при изменении ориентации телефона, так и при редактировании элемента и возврате его в список.
Ответ 4
Очень простой способ:
/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();
//Here u should save the currentPosition anywhere
/** Restore the previus saved position **/
listView.setSelection(savedPosition);
Метод setSelection будет reset список поставляемого элемента. Если не в режиме касания, элемент будет выбран, если в касательном режиме элемент будет отображаться только на экране.
Более сложный подход:
listView.setOnScrollListener(this);
//Implements the interface:
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mCurrentX = view.getScrollX();
mCurrentY = view.getScrollY();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
//Save anywere the x and the y
/** Restore: **/
listView.scrollTo(savedX, savedY);
Ответ 5
Я нашел в этом что-то интересное.
Я попробовал setSelection и scrolltoXY, но он вообще не работал, список остался в той же позиции, после некоторых проб и ошибок я получил следующий код, который работает
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {
@Override
public void run() {
list.setSelection(0);
}
});
Если вместо публикации Runnable вы пытаетесь выполнить runOnUiThread, он не работает (по крайней мере, на некоторых устройствах)
Это очень странное решение для чего-то, что должно быть прямым.
Ответ 6
ВНИМАНИЕ!! В AbsListView есть ошибка, которая не позволяет корректной работе onSaveState(), если ListView.getFirstVisiblePosition() равен 0.
Итак, если у вас большие изображения, которые занимают большую часть экрана, и вы переходите ко второму изображению, но немного из первого показывает, положение прокрутки не будет сохранено...
от AbsListView.java:1650 (комментарии мои)
// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
...
} else {
ss.viewTop = 0;
ss.firstId = INVALID_POSITION;
ss.position = 0;
}
Но в этой ситуации "верх" в приведенном ниже коде будет отрицательным числом, которое вызывает другие проблемы, которые предотвращают правильное восстановление состояния. Поэтому, когда "верх" отрицательный, получите следующий дочерний
// save index and top position
int index = getFirstVisiblePosition();
View v = getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
if (top < 0 && getChildAt(1) != null) {
index++;
v = getChildAt(1);
top = v.getTop();
}
// parcel the index and top
// when restoring, unparcel index and top
listView.setSelectionFromTop(index, top);
Ответ 7
private Parcelable state;
@Override
public void onPause() {
state = mAlbumListView.onSaveInstanceState();
super.onPause();
}
@Override
public void onResume() {
super.onResume();
if (getAdapter() != null) {
mAlbumListView.setAdapter(getAdapter());
if (state != null){
mAlbumListView.requestFocus();
mAlbumListView.onRestoreInstanceState(state);
}
}
}
Достаточно
Ответ 8
Для некоторых, кто ищет решение этой проблемы, корень проблемы может быть там, где вы устанавливаете адаптер списка видов. После того, как вы установите адаптер в списке, он сбрасывает положение прокрутки. Просто подумать. Я переместил адаптер в мой onCreateView после того, как мы захватили ссылку на список, и он решил проблему для меня. =)
Ответ 9
Я публикую это, потому что я удивлен, что никто не упомянул об этом.
После того, как пользователь нажмет кнопку "Назад", он вернется к списку в том же состоянии, в котором он вышел.
Этот код будет переопределять кнопку "вверх", чтобы вести себя так же, как кнопка "назад", поэтому в случае Listview → Details → Back to Listview (и других параметров) это простейший код для поддержки прокрутки и содержимое в списке.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return(true);
}
return(super.onOptionsItemSelected(item)); }
Предостережение: если вы можете перейти к другому действию из активности деталей, кнопка "вверх" вернет вас обратно к этой активности, поэтому вам придется манипулировать историей задней панели, чтобы это работало.
Ответ 10
Не просто android:saveEnabled="true"
в объявлении xml ListView достаточно?
Ответ 11
ЛУЧШЕЕ РЕШЕНИЕ:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.post(new Runnable() {
@Override
public void run() {
mList.setSelectionFromTop(index, top);
}
});
ВЫ ДОЛЖНЫ ПРИЗЫВАТЬ В ПОЧТУ И В РЕЗЬБЕ!
Ответ 12
Вы можете сохранить состояние прокрутки после перезагрузки, если вы сохраните состояние до перезагрузки и восстановления после него. В моем случае я сделал асинхронный сетевой запрос и перезагрузил список в обратном вызове после его завершения. Здесь я восстанавливаю состояние. Образец кода - это Kotlin.
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() { newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
}
Ответ 13
Если вы используете фрагменты, размещенные в действии, вы можете сделать что-то вроде этого:
public abstract class BaseFragment extends Fragment {
private boolean mSaveView = false;
private SoftReference<View> mViewReference;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mSaveView) {
if (mViewReference != null) {
final View savedView = mViewReference.get();
if (savedView != null) {
if (savedView.getParent() != null) {
((ViewGroup) savedView.getParent()).removeView(savedView);
return savedView;
}
}
}
}
final View view = inflater.inflate(getFragmentResource(), container, false);
mViewReference = new SoftReference<View>(view);
return view;
}
protected void setSaveView(boolean value) {
mSaveView = value;
}
}
public class MyFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setSaveView(true);
final View view = super.onCreateView(inflater, container, savedInstanceState);
ListView placesList = (ListView) view.findViewById(R.id.places_list);
if (placesList.getAdapter() == null) {
placesList.setAdapter(createAdapter());
}
}
}
Ответ 14
Если вы сохраняете/восстанавливаете положение прокрутки ListView
самостоятельно, вы по существу дублируете функциональность, уже внедренную в инфраструктуру Android. ListView
восстанавливает точное положение прокрутки только по своему усмотрению, за исключением одного оговорки: поскольку @aaronvargas упоминает, что есть ошибка в AbsListView
, которая не позволит восстановить точную позицию прокрутки для первого элемента списка. Тем не менее, лучший способ восстановить положение прокрутки - не восстановить его. Рамка для Android сделает это лучше для вас. Просто убедитесь, что вы выполнили следующие условия:
- убедитесь, что вы не вызывали метод
setSaveEnabled(false)
и не устанавливали атрибутandroid:saveEnabled="false"
для списка в файле макета xml - для
ExpandableListView
переопределить методlong getCombinedChildId(long groupId, long childId)
, чтобы он возвращал положительное длинное число (реализация по умолчанию в классеBaseExpandableListAdapter
возвращает отрицательное число). Вот примеры:
.
@Override
public long getChildId(int groupPosition, int childPosition) {
return 0L | groupPosition << 12 | childPosition;
}
@Override
public long getCombinedChildId(long groupId, long childId) {
return groupId << 32 | childId << 1 | 1;
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getCombinedGroupId(long groupId) {
return (groupId & 0x7FFFFFFF) << 32;
}
- Если
ListView
илиExpandableListView
используется во фрагменте, не воссоздавайте фрагмент при активном отдыхе (например, после вращения экрана). Получить фрагмент с помощью методаfindFragmentByTag(String tag)
. - убедитесь, что
ListView
имеетandroid:id
, и он уникален.
Чтобы избежать вышеупомянутого оговорки с первым элементом списка, вы можете создать свой адаптер таким образом, чтобы он возвращал специальный вид нулевого пикселя высоты изображения для ListView
в позиции 0.
Вот простой пример показывает, что ListView
и ExpandableListView
восстанавливают свои тонкие позиции прокрутки, тогда как их позиции прокрутки явно не сохраняются/не восстанавливаются. Точная позиция прокрутки восстанавливается отлично даже для сложных сценариев с временным переключением на другое приложение, двойным поворотом экрана и переключением обратно в тестовое приложение. Обратите внимание: если вы явно выходите из приложения (нажав кнопку "Назад" ), положение прокрутки не будет сохранено (так же как все остальные виды не сохранят свое состояние).
https://github.com/voromto/RestoreScrollPosition/releases
Ответ 15
Для действия, полученного из ListActivity, который реализует LoaderManager.LoaderCallbacks с использованием SimpleCursorAdapter, он не работал, чтобы восстановить позицию в onReset(), потому что активность почти всегда перезапускалась, и адаптер был перезагружен при закрытии представления деталей. Трюк состоял в том, чтобы восстановить позицию в onLoadFinished():
в onListItemClick():
// save the selected item position when an item was clicked
// to open the details
index = getListView().getFirstVisiblePosition();
View v = getListView().getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - getListView().getPaddingTop());
в onLoadFinished():
// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);
в onBackPressed():
// Show the top item at next start of the app
index = 0;
top = 0;
Ответ 16
Похоже, ни одно из предложенных здесь решений не сработало для меня. В моем случае у меня есть ListView
во Fragment
который я заменяю в FragmentTransaction
, поэтому каждый раз, когда отображается FragmentTransaction
, создается новый экземпляр Fragment
, что означает, что состояние ListView
не может быть сохранено как элемент Fragment
,
Вместо этого я в конечном итоге сохранил состояние в своем пользовательском классе Application
. Код ниже должен дать вам представление о том, как это работает:
public class MyApplication extends Application {
public static HashMap<String, Parcelable> parcelableCache = new HashMap<>();
/* ... code omitted for brevity ... */
}
public class MyFragment extends Fragment{
private ListView mListView = null;
private MyAdapter mAdapter = null;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAdapter = new MyAdapter(getActivity(), null, 0);
mListView = ((ListView) view.findViewById(R.id.myListView));
Parcelable listViewState = MyApplication.parcelableCache.get("my_listview_state");
if( listViewState != null )
mListView.onRestoreInstanceState(listViewState);
}
@Override
public void onPause() {
MyApplication.parcelableCache.put("my_listview_state", mListView.onSaveInstanceState());
super.onPause();
}
/* ... code omitted for brevity ... */
}
Основная идея заключается в том, что вы сохраняете состояние вне экземпляра фрагмента. Если вам не нравится идея иметь статическое поле в классе приложения, я думаю, вы могли бы сделать это, реализовав фрагментный интерфейс и сохранив состояние в вашей активности.
Другим решением было бы сохранить его в SharedPreferences
, но это становится немного сложнее, и вам нужно будет обязательно очистить его при запуске приложения, если вы не хотите, чтобы состояние сохранялось при запуске приложения.
Кроме того, чтобы избежать "позиции прокрутки не сохраняется, когда первый элемент виден", вы можете отобразить фиктивный первый элемент с 0px
высотой. Это может быть достигнуто путем переопределения getView()
в вашем адаптере, например:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if( position == 0 ) {
View zeroHeightView = new View(parent.getContext());
zeroHeightView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
return zeroHeightView;
}
else
return super.getView(position, convertView, parent);
}
Ответ 17
Мой ответ для Firebase, а позиция 0 - это обходной путь
Parcelable state;
DatabaseReference everybody = db.getReference("Everybody Room List");
everybody.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
state = listView.onSaveInstanceState(); // Save
progressBar.setVisibility(View.GONE);
arrayList.clear();
for (DataSnapshot messageSnapshot : dataSnapshot.getChildren()) {
Messages messagesSpacecraft = messageSnapshot.getValue(Messages.class);
arrayList.add(messagesSpacecraft);
}
listView.setAdapter(convertView);
listView.onRestoreInstanceState(state); // Restore
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
и convertView
позиция 0 добавить пустой элемент, который вы не используете
public class Chat_ConvertView_List_Room extends BaseAdapter {
private ArrayList<Messages> spacecrafts;
private Context context;
@SuppressLint("CommitPrefEdits")
Chat_ConvertView_List_Room(Context context, ArrayList<Messages> spacecrafts) {
this.context = context;
this.spacecrafts = spacecrafts;
}
@Override
public int getCount() {
return spacecrafts.size();
}
@Override
public Object getItem(int position) {
return spacecrafts.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@SuppressLint({"SetTextI18n", "SimpleDateFormat"})
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.message_model_list_room, parent, false);
}
final Messages s = (Messages) this.getItem(position);
if (position == 0) {
convertView.getLayoutParams().height = 1; // 0 does not work
} else {
convertView.getLayoutParams().height = RelativeLayout.LayoutParams.WRAP_CONTENT;
}
return convertView;
}
}
Я видел эту работу временно, не мешая пользователю, я надеюсь, что она работает для вас
Ответ 18
используйте этот код ниже:
int index,top;
@Override
protected void onPause() {
super.onPause();
index = mList.getFirstVisiblePosition();
View v = challengeList.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
}
и всякий раз, когда вы обновляете свои данные, используйте следующий код:
adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
Ответ 19
Я использую FirebaseListAdapter и не смог заставить работать ни одно из решений. Я закончил тем, что делал это. Я предполагаю, что есть более элегантные способы, но это полное и рабочее решение.
Перед созданием:
private int reset;
private int top;
private int index;
Внутри FirebaseListAdapter:
@Override
public void onDataChanged() {
super.onDataChanged();
// Only do this on first change, when starting
// activity or coming back to it.
if(reset == 0) {
mListView.setSelectionFromTop(index, top);
reset++;
}
}
OnStart:
@Override
protected void onStart() {
super.onStart();
if(adapter != null) {
adapter.startListening();
index = 0;
top = 0;
// Get position from SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
top = sharedPref.getInt("TOP_POSITION", 0);
index = sharedPref.getInt("INDEX_POSITION", 0);
// Set reset to 0 to allow change to last position
reset = 0;
}
}
OnStop:
@Override
protected void onStop() {
super.onStop();
if(adapter != null) {
adapter.stopListening();
// Set position
index = mListView.getFirstVisiblePosition();
View v = mListView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
// Save position to SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
}
}
Так как я также должен был решить эту проблему для FirebaseRecyclerAdapter, я также выкладываю здесь решение для этого:
Перед созданием:
private int reset;
private int top;
private int index;
Внутри FirebaseRecyclerAdapter:
@Override
public void onDataChanged() {
// Only do this on first change, when starting
// activity or coming back to it.
if(reset == 0) {
linearLayoutManager.scrollToPositionWithOffset(index, top);
reset++;
}
}
OnStart:
@Override
protected void onStart() {
super.onStart();
if(adapter != null) {
adapter.startListening();
index = 0;
top = 0;
// Get position from SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
top = sharedPref.getInt("TOP_POSITION", 0);
index = sharedPref.getInt("INDEX_POSITION", 0);
// Set reset to 0 to allow change to last position
reset = 0;
}
}
OnStop:
@Override
protected void onStop() {
super.onStop();
if(adapter != null) {
adapter.stopListening();
// Set position
index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
// Save position to SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
}
}
Ответ 20
Чтобы уточнить отличный ответ Райана Ньюсома и настроить его для фрагментов и для обычного случая, когда мы хотим перейти от "основного" фрагмента ListView к "подробному" фрагменту и затем вернуться к "главному" "
private View root;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
if(root == null){
root = inflater.inflate(R.layout.myfragmentid,container,false);
InitializeView();
}
return root;
}
public void InitializeView()
{
ListView listView = (ListView)root.findViewById(R.id.listviewid);
BaseAdapter adapter = CreateAdapter();//Create your adapter here
listView.setAdpater(adapter);
//other initialization code
}
"Волшебство" заключается в том, что когда мы возвращаемся от фрагмента сведений к фрагменту ListView, представление не воссоздается, мы не устанавливаем адаптер ListView, поэтому все остается, как мы его оставили!