Я долго, ListView
что пользователь может прокручивать перед возвратом к предыдущему экрану. Когда пользователь откроет это ListView
снова, я хочу, чтобы список был прокручен до той же точки, что и ранее. Есть идеи как этого добиться?
Я долго, ListView
что пользователь может прокручивать перед возвратом к предыдущему экрану. Когда пользователь откроет это ListView
снова, я хочу, чтобы список был прокручен до той же точки, что и ранее. Есть идеи как этого добиться?
Ответы:
Попробуй это:
// 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
.
int index = mList.getFirstVisiblePosition();
и только одна линия для восстановления: mList.setSelectionFromTop(index, 0);
. Отличный ответ (+1)! Я искал элегантное решение этой проблемы.
Parcelable state;
@Override
public void onPause() {
// Save ListView state @ onPause
Log.d(TAG, "saving listview state");
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);
}
}
Я принял решение, предложенное @ (Кирк Волл), и оно работает для меня. Я также видел в исходном коде 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 это работает как при изменении ориентации телефона, так и при редактировании элемента и возвращении к списку.
mListState = getListView().onSaveInstanceState().
основном при ротации устройства.
onRestoreInstanceState
никогда не называется :(
Очень простой способ:
/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();
//Here u should save the currentPosition anywhere
/** Restore the previus saved position **/
listView.setSelection(savedPosition);
Метод setSelection сбрасывает список до предоставленного элемента. Если не в сенсорном режиме, элемент будет фактически выбран, если в сенсорном режиме он будет размещен только на экране.
Более сложный подход:
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);
Я нашел что-то интересное по этому поводу.
Я попробовал setSelection и scrolltoXY, но он не работал вообще, список остался в том же положении, после некоторых проб и ошибок я получил следующий код, который работает
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {
@Override
public void run() {
list.setSelection(0);
}
});
Если вместо публикации Runnable вы пытаетесь запустить runOnUiThread, он также не работает (по крайней мере, на некоторых устройствах)
Это очень странный обходной путь для чего-то, что должно быть прямым.
setSelectionFromTop()
не работает.
ВНИМАНИЕ !! В 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);
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);
}
}
}
Достаточно
Для тех, кто ищет решение этой проблемы, корень проблемы может быть в том, где вы настраиваете свой адаптер представления списка. После того, как вы установите адаптер в списке, он сбрасывает позицию прокрутки. Просто кое-что рассмотреть. Я переместил установку адаптера в свой onCreateView после того, как мы получили ссылку на просмотр списка, и это решило проблему для меня. знак равно
Отправляю это, потому что я удивлен, что никто не упомянул это.
После того, как пользователь нажмет кнопку «Назад», он вернется к списку в том же состоянии, в котором он вышел из него.
Этот код переопределяет кнопку «вверх», чтобы вести себя так же, как кнопка «назад», поэтому в случае 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)); }
Предостережение: если вы можете перейти к другому действию из подробного действия, кнопка вверх вернет вас к этому действию, поэтому вам придется манипулировать историей кнопок, чтобы это работало.
Разве недостаточно просто android:saveEnabled="true"
в объявлении ListView xml?
ЛУЧШЕЕ РЕШЕНИЕ:
// 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);
}
});
ВЫ ДОЛЖНЫ ЗВОНИТЬ В ПОЧТУ И В РЕЗЬБЕ!
Вы можете сохранить состояние прокрутки после перезагрузки, если сохраните состояние до перезагрузки и восстановите его после. В моем случае я сделал асинхронный сетевой запрос и перезагрузил список в обратном вызове после его завершения. Здесь я восстанавливаю состояние. Пример кода - Котлин.
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() { newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
}
Если вы используете фрагменты, размещенные в действии, вы можете сделать что-то вроде этого:
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());
}
}
}
Если вы сохраняете / восстанавливаете свою позицию прокрутки, ListView
вы по сути дублируете функциональность, уже реализованную в платформе Android. Сама по себе ListView
точная позиция прокрутки восстанавливается сама по себе, за исключением одного предупреждения: как упомянул @aaronvargas, есть ошибка AbsListView
, которая не позволяет восстановить позицию точной прокрутки для первого элемента списка. Тем не менее, лучший способ восстановить положение прокрутки - не восстанавливать его. Android Framework сделает это лучше для вас. Просто убедитесь, что вы выполнили следующие условия:
setSaveEnabled(false)
метод и не задали android:saveEnabled="false"
атрибут для списка в файле макета xmlExpandableListView
переопределения, 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
Для действия, производного от 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;
Похоже, ни одно из предложенных здесь решений не сработало для меня. В моем случае у меня есть переменная ListView
в, Fragment
которую я заменяю в 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);
}
Мой ответ для 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;
}
}
Я видел эту работу временно, не мешая пользователю, я надеюсь, что она работает для вас
используйте этот код ниже:
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);
Я использую 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();
}
}
Чтобы уточнить отличный ответ Райана Ньюсома и настроить его для фрагментов и для обычного случая, когда мы хотим перейти от «основного» фрагмента 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, поэтому все остается, как мы его оставили!