Сохранение / сохранение / восстановление позиции прокрутки при возврате в ListView - PullRequest
385 голосов
/ 10 июня 2010

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

Ответы [ 19 ]

1 голос
/ 05 июля 2013

Я публикую это, потому что я удивлен, что никто не упомянул это.

После того, как пользователь нажмет кнопку «Назад», он вернется к списку в том же состоянии, в котором он вышел из него.

Этот код переопределяет кнопку «вверх» так, чтобы она работала так же, как кнопка «назад», поэтому в случае 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)); }

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

1 голос
/ 08 февраля 2014

Не достаточно ли просто android:saveEnabled="true" в объявлении ListView xml?

0 голосов
/ 18 апреля 2019

Я использую 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();
    }
}
0 голосов
/ 09 апреля 2019

используйте следующий код:

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);
0 голосов
/ 21 сентября 2018

Мой ответ для 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;
}
}

Я видел эту работу временно, не беспокоя пользователя, надеюсь, она у вас работает

0 голосов
/ 05 апреля 2018

Ни одно из предложенных здесь решений, похоже, не работает для меня. В моем случае у меня есть 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);
}
0 голосов
/ 17 августа 2016

Для действия, полученного из 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;
0 голосов
/ 06 сентября 2015

Если вы сохраняете / восстанавливаете позицию прокрутки ListView самостоятельно, вы по сути дублируете функциональность, уже реализованную в платформе Android. ListView сам по себе хорошо восстанавливает позицию точной прокрутки, за исключением одного предупреждения: как упоминал @aaronvargas, в AbsListView есть ошибка, которая не позволяет восстановить позицию точной прокрутки для первого элемента списка. Тем не менее, лучший способ восстановить положение прокрутки - не восстанавливать его. Android Framework сделает это лучше для вас. Просто убедитесь, что вы выполнили следующие условия:

  • убедитесь, что вы не вызвали метод 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

0 голосов
/ 24 сентября 2014

Если вы используете фрагменты, размещенные на мероприятии, вы можете сделать что-то вроде этого:

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());
          }
     }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...