Поддерживать / сохранить / восстановить положение прокрутки при возврате в список

StackOverflow https://stackoverflow.com/questions/3014089

Вопрос

У меня длинный 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.

Другие советы

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

Я принял решение, предложенный @ (Kirk Woll), и это работает для меня. Я также видел в исходном коде Android для приложения «Контакты», которые они используют аналогичную технику. Я хотел бы добавить еще несколько деталей: на вершине на мой класс, полученный в течение вличия:

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 это работает как, когда вы меняете ориентацию телефона, и когда вы редактируете элемент и вернитесь к списку.

Очень простой способ:

/** 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);
    }
});

Если вместо того, чтобы разместить родительский, вы попробуйте RONONUITHREAD, он тоже не работает (по крайней мере, на некоторых устройствах)

Это очень странный обходной путь для чего-то, что должно быть прямо вперед.

ОСТОРОЖНОСТЬ!! В 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;
}

Но в этой ситуации «Top» в следующем ниже будет негативным числом, которое вызывает другие проблемы, которые препятствуют правильному восстановлению государства. Поэтому, когда «верх» отрицательно, получите следующий ребенок

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

Достаточно

Для некоторых ищет решения этой проблемы, корень вопроса может быть там, где вы устанавливаете адаптер просмотра списка. После установки адаптера на ListView он сбрасывает положение прокрутки. Просто что-то рассмотреть. Я переместил настроив адаптер в мой OncreateView после того, как мы схватием ссылку на список ListView, и она решила для меня проблемы. знак равно

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

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

Этот код переопределят кнопку «Вверх», чтобы вести себя так же, как кнопка «Назад», так что в случае ListView -> Детали -> Вернуться к ListView (и никаких других вариантов) Это самый простой код для поддержания прокрутки и содержимого в ListView.

 public boolean onOptionsItemSelected(MenuItem item) {
     switch (item.getItemId()) {
         case android.R.id.home:
             onBackPressed();
             return(true);
     }
     return(super.onOptionsItemSelected(item)); }

ВНИМАНИЕ! Если вы можете перейти к другому действию из деталей, активность, кнопка UP вернет вас к тому, что вам придется манипулировать историей Backbutton, чтобы это было работать.

Не просто android:saveEnabled="true" В объявлении XML 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.post(new Runnable() {
    @Override
    public void run() {
      mList.setSelectionFromTop(index, top);
   }
});

Вы должны позвонить в пост и в потоке!

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

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 Framework. То 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/Relestes

Для активности, полученной из подразвиты, которая реализует LASTERMANAGER.LoaderClaallbacks, используя SimpleCursorAdapter, он не работал, чтобы восстановить положение в OnReset (), поскольку активность почти всегда перезапускалась, и адаптер перезагружался, когда представление деталей было закрыто. Хитрость заключалась в том, чтобы восстановить положение в OnloadFineed ():

В OnlistiteMlick ():

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

В onloadfineed ():

// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);

в обратной стороне ():

// 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 A Добавьте пустой предмет, который вы не используете

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();
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top