Zachowaj / Zapisz/Przywróć pozycję przewijania podczas powrotu do widoku listy

Mam długi ListView, który użytkownik może przewijać przed powrotem do poprzedniego ekranu. Gdy użytkownik ponownie otworzy tę ListView, chcę, aby lista została przewinięta do tego samego punktu, co poprzednio. Jakieś pomysły, jak to osiągnąć?

Author: Jonik, 2010-06-10

17 answers

Spróbuj tego:

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

Wyjaśnienie:

ListView.getFirstVisiblePosition() zwraca górną widoczną pozycję listy. Ale ten element może być częściowo przewinięty poza widok, a jeśli chcesz przywrócić dokładną pozycję przewijania listy, musisz uzyskać to przesunięcie. Tak więc ListView.getChildAt(0) zwraca {[3] } dla elementu top list, a następnie View.getTop() - mList.getPaddingTop() zwraca jego względne przesunięcie od góry ListView. Następnie, aby przywrócić pozycję przewijania ListView, wywołujemy {[7] } z indeksem żądanego elementu i przesunięciem do pozycji jego Góry krawędź od góry ListView.

 618
Author: ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-05 09:23:12
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);
    }
}
 517
Author: Eugene Mymrin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-19 10:37:21

Przyjęłam rozwiązanie zaproponowane przez @ (Kirk Woll) i działa dla mnie. Widziałem również w kodzie źródłowym Androida dla aplikacji "Kontakty", że używają podobnej techniki. Chciałbym dodać więcej szczegółów: Na górze mojej listy:

private static final String LIST_STATE = "listState";
private Parcelable mListState = null;

Wtedy jakaÅ› metoda nadpisuje:

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

Oczywiście" loadData " jest moją funkcją do pobierania danych z DB i umieszczania ich na liście.

Na moim urządzeniu Froyo działa to zarówno po zmianie telefonu orientacji, a po edycji elementu i powrocie do listy.
 51
Author: Giorgio Barchiesi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-17 18:44:09

Bardzo prosty sposób:

/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();

//Here u should save the currentPosition anywhere

/** Restore the previus saved position **/
listView.setSelection(savedPosition);

Metoda setSelection spowoduje zresetowanie listy do dostarczonego elementu. Jeśli nie w trybie dotykowym element zostanie wybrany, jeśli w trybie dotykowym element zostanie umieszczony tylko na ekranie.

Bardziej skomplikowane podejście:

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);
 27
Author: Francesco Laurita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 20:03:55

Znalazłem w tym coś ciekawego.

Próbowałem setSelection i scrolltoXY, ale w ogóle nie działało, lista pozostała w tej samej pozycji, po próbach i błędach dostałem następujący kod, który działa

final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {            
    @Override
    public void run() {
        list.setSelection(0);
    }
});

Jeśli zamiast publikować Runnable spróbujesz runOnUiThread to też nie działa (przynajmniej na niektórych urządzeniach)

To bardzo dziwne obejście dla czegoś, co powinno być proste.
 17
Author: shalafi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 19:49:08

Uwaga!! W AbsListView jest błąd, który nie pozwala onsavestate () działać poprawnie, jeśli ListView.getFirstVisiblePosition() wynosi 0.

Więc jeśli masz duże obrazy, które zajmują większość ekranu i przewijasz do drugiego obrazu, ale wyświetla się trochę pierwszego, pozycja przewijania nie zostanie zapisana...

From AbsListView.Java: 1650 (komentarze moje)

// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
    ...
} else {
    ss.viewTop = 0;
    ss.firstId = INVALID_POSITION;
    ss.position = 0;
}

Ale w tej sytuacji "góra" w poniższym kodzie będzie liczbą ujemną co powoduje inne problemy uniemożliwiające prawidłowe przywrócenie stanu. Więc jeśli' góra ' jest ujemna, uzyskaj następne dziecko

// 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);
 10
Author: aaronvargas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 20:03:15
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);
        }
    }
}

Wystarczy

 4
Author: Leo Phan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-24 02:15:00

Dla niektórych poszukujących rozwiązania tego problemu głównym problemem może być ustawienie karty widoków listy. Po ustawieniu adaptera w widoku listy zresetuje on pozycję przewijania. Tylko coś do rozważenia. Przeniosłem ustawienie adaptera do mojego onCreateView po złapaniu odniesienia do listview i to rozwiązało problem dla mnie. =)

 3
Author: Ryan Newsom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-25 20:25:18

Zamieszczam to, ponieważ dziwię się, że nikt o tym nie wspomniał.

Po kliknięciu przycisku Wstecz użytkownik powróci do listview w tym samym stanie, w jakim z niego wyszedł.

Ten kod nadpisuje przycisk" w górę", aby zachowywał się tak samo jak przycisk wstecz, więc w przypadku Listview -> Details -> Back To Listview (i żadnych innych opcji) jest to najprostszy kod do utrzymania scrollposition i zawartości w listview.

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

Uwaga: Jeśli możesz przejść do kolejna aktywność z aktywności szczegóły przycisk w górę przywróci cię z powrotem do tej aktywności, więc będziesz musiał manipulować historią backbutton, aby to zadziałało.

 1
Author: Mazvél,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-05 11:18:21

Czy po prostu android:saveEnabled="true" w deklaracji ListView xml nie wystarczy?

 1
Author: Andrea Richiardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-07 23:48:07

NAJLEPSZYM ROZWIÄ„ZANIEM JEST:

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

MUSISZ ZADZWONIĆ W POŚCIE I W WĄTKU!

 1
Author: Andrew Sneck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-13 17:55:30

Możesz zachować stan przewijania po przeładowaniu, jeśli zapiszesz stan przed przeładowaniem i przywrócisz go po przeładowaniu. W moim przypadku zrobiłem asynchroniczne żądanie sieciowe i przeładowałem listę w wywołaniu zwrotnym po jego zakończeniu. Tu przywracam stan. Próbka kodu to Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

getNewThings() { newThings: List<Thing> ->

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}
 1
Author: Michael Peterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-17 19:44:14

Jeśli używasz fragmentów hostowanych na aktywności, możesz zrobić coś takiego:

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());
          }
     }
}
 0
Author: saulobrito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 16:21:10

Jeśli sam zapisujesz / przywracasz pozycję przewijania ListView, zasadniczo powielasz funkcjonalność już zaimplementowaną w Android frameworku. ListView przywraca pozycję przewijania dokładnego samodzielnie, z wyjątkiem jednego zastrzeżenia: jak wspomniał @aaronvargas, w AbsListView jest błąd, który nie pozwala przywrócić pozycji przewijania dokładnego dla pierwszego elementu listy. Jednak najlepszym sposobem przywrócenia pozycji przewijania nie jest jej przywrócenie. Android framework zrobi to lepiej dla Ciebie. Upewnij się tylko, że spełnione zostały następujące warunki:

  • upewnij siÄ™, że nie wywoÅ‚aÅ‚eÅ› metody setSaveEnabled(false) i nie ustawiÅ‚eÅ› atrybutu android:saveEnabled="false" dla listy w pliku ukÅ‚adu XML
  • for ExpandableListView override long getCombinedChildId(long groupId, long childId) metoda tak, że zwraca dodatniÄ… liczbÄ™ dÅ‚ugÄ… (DomyÅ›lna implementacja w klasie BaseExpandableListAdapter Zwraca liczbÄ™ ujemnÄ…). Oto przykÅ‚ady:

.

@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;
}
  • jeÅ›li ListView lub ExpandableListView jest używane w fragmencie, nie odtwarzaj tego fragmentu podczas odtwarzania aktywnoÅ›ci (po obróceniu ekranu na przykÅ‚ad). Uzyskaj fragment za pomocÄ… metody findFragmentByTag(String tag).
  • upewnij siÄ™, że ListView ma android:id i jest unikalny.

Aby uniknąć powyższego zastrzeżenia z pierwszym elementem listy, możesz stworzyć swój adapter w sposób, w jaki zwraca on specjalny atrapa zerowego widoku wysokości pikseli dla ListView na pozycji 0. Oto prosty przykładowy projekt pokazuje ListView i ExpandableListView przywracają swoje drobne pozycje przewijania, podczas gdy ich pozycje przewijania nie są jawnie zapisywane / przywracane. Przywracana jest pozycja przewijania doskonale nawet w złożonych scenariuszach z tymczasowym przełączeniem na inną aplikację, podwójnym obrotem ekranu i przełączeniem z powrotem do aplikacji testowej. Należy pamiętać, że jeśli bezpośrednio opuszczasz aplikację (naciskając przycisk Wstecz), pozycja przewijania nie zostanie zapisana (podobnie jak wszystkie inne widoki nie zapiszą swojego stanu). https://github.com/voromto/RestoreScrollPosition/releases

 0
Author: Sergey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-07 13:19:24

Dla aktywności pochodzącej z ListActivity, która implementuje LoaderManager.LoaderCallbacks używając SimpleCursorAdapter nie udało się przywrócić pozycji w onreset (), ponieważ aktywność była prawie zawsze restartowana, a adapter był przeładowywany po zamknięciu widoku szczegółów. Trik polegał na przywróceniu pozycji w onLoadFinished (): {]}

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

W onLoadFinished():

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

W onBackPressed():

// Show the top item at next start of the app
index = 0;
top = 0;
 0
Author: Perotin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-17 12:53:57

Żadne z oferowanych tutaj rozwiązań nie wydawało mi się skuteczne. W moim przypadku mam ListView w Fragment, które zastępuję w FragmentTransaction, więc nowa instancja Fragment jest tworzona za każdym razem, gdy pokazywany jest fragment, co oznacza, że stan ListView nie może być przechowywany jako członek Fragment.

Zamiast tego, skończyło się na przechowywaniu stanu w mojej niestandardowej klasie Application. Poniższy kod powinien dać ci wyobrażenie, jak to działa:

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 ... */

}

Podstawową ideą jest to, że przechowujesz stan poza instancją fragmentu. Jeśli nie podoba ci się pomysł posiadania statycznego pola w klasie aplikacji, myślę, że możesz to zrobić, implementując interfejs fragment i zapisując stan w swojej aktywności.

Innym rozwiązaniem byłoby przechowywanie go w SharedPreferences, ale robi się to nieco bardziej skomplikowane i musisz upewnić się, że wyczyścisz go przy uruchamianiu aplikacji, chyba że chcesz, aby stan był utrzymywany podczas uruchamiania aplikacji.

 

Również, aby uniknąć "pozycja przewijania nie jest zapisana, gdy pierwszy element jest widoczny", możesz wyświetlić atrapę pierwszego elementu o wysokości 0px. Można to osiągnąć poprzez nadpisanie getView() w adapterze, w następujący sposób:

@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
Author: BadCash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-05 13:19:12

Moja odpowiedź jest dla Firebase i pozycja 0 jest obejściem

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

I convertView

Pozycja 0 a dodaj pusty element, którego nie używasz

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

Widziałem to działanie tymczasowo bez przeszkadzania użytkownikowi, mam nadzieję, że działa dla ciebie

 0
Author: Trk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-21 13:59:21