Zachowaj / zapisz / przywróć pozycję przewijania podczas powrotu do widoku listy


397

Mam długi czas, ListViewże użytkownik może przewijać przed powrotem do poprzedniego ekranu. Gdy użytkownik otworzy to ListViewponownie, chcę, aby lista została przewinięta do tego samego miejsca, w którym była poprzednio. Wszelkie pomysły, jak to osiągnąć?


22
Myślę, że rozwiązania wspomniane przez Eugene'a Mymrina / Giorgio Barchiesiego są lepsze niż zaakceptowana odpowiedź
Mira Weller,

Odpowiedzi:


631

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

Objaśnienie:

ListView.getFirstVisiblePosition()zwraca najwyżej widoczny element listy. Ale ten element może być częściowo przewijany poza zasięgiem wzroku, a jeśli chcesz przywrócić dokładną pozycję przewijania listy, musisz uzyskać to przesunięcie. ListView.getChildAt(0)Zwraca więc pozycję Viewdla górnej listy, a następnie View.getTop() - mList.getPaddingTop()zwraca jej względne przesunięcie od góry ListView. Następnie, aby przywrócić ListViewpozycję przewijania, wywołujemy ListView.setSelectionFromTop()z indeksem pożądanego elementu i przesunięciem, aby ustawić jego górną krawędź od góry ListView.


2
Nie bardzo rozumiem, jak to działa. Używam tylko listView.getFirstVisiblePosition () i rozumiem to, ale nie jestem pewien, co się tutaj dzieje. Czy jest jakaś szansa na krótkie wyjaśnienie? :)
HXCaine

56
Dlatego ListView.getFirstVisiblePosition () zwraca najwyżej widoczny element listy. Ale ten element może być częściowo przewijany poza zasięgiem wzroku, a jeśli chcesz przywrócić dokładną pozycję przewijania listy, musisz uzyskać to przesunięcie. Tak więc ListView.getChildAt (0) zwraca widok dla górnej pozycji listy, a następnie View.getTop () zwraca względne przesunięcie od górnej krawędzi ListView. Następnie, aby przywrócić pozycję przewijania ListView, wywołujemy ListView.setSelectionFromTop () z indeksem pożądanego elementu i przesunięciem, aby ustawić jego górną krawędź od góry ListView. Wszystko jasne?
ian

15
To może stać się jeszcze prostsze używać jedną linię zapisać: int index = mList.getFirstVisiblePosition();i tylko jedną linię, aby przywrócić: mList.setSelectionFromTop(index, 0);. Świetna odpowiedź (+1)! Szukałem eleganckiego rozwiązania tego problemu.
Phil

17
@Phil Twoje uproszczone rozwiązanie nie działa, aby przywrócić dokładną pozycję przewijania.
Ixx

2
jak powiedział @nbarraille, kod powinien brzmieć bardziej jak „v.getTop () - mList.getPaddingTop ().” W przeciwnym razie spędzisz godzinę, tak jak ja, próbując dowiedzieć się, dlaczego zawsze przywraca włosy tylko ...
jdowdell

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

106
Myślę, że to najlepsza odpowiedź, ponieważ ta metoda przywraca dokładną pozycję przewijania, a nie tylko pierwszy widoczny element.
Mira Weller,

9
Świetna odpowiedź, ale nie zadziała podczas dynamicznego ładowania treści i chcesz zapisać stan w metodzie onPause ().
Gio

13
świetne rozwiązanie. Tylko jeden problem. Jeśli elementy są duże i przewijasz, ale pierwszy element jest nadal widoczny, lista przeskakuje z powrotem na górę. jeśli przewiniesz do drugiego elementu lub gdziekolwiek indziej wszystko działa
passsy

4
@passsy Czy kiedykolwiek znalazłeś rozwiązanie, które skacze z powrotem na górę?
Papajohn000

2
Jak powiedział aaronvargas, nie może to działać, gdy ListView.getFirstVisiblePosition () ma wartość 0.
VinceStyling

54

Przyjąłem rozwiązanie zaproponowane przez @ (Kirk Woll) i działa dla mnie. Widziałem także 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 klasy pochodnej ListActivity:

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

Następnie niektóre metody zastępują:

@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” to moja funkcja do pobierania danych z bazy danych i umieszczania ich na liście.

W moim urządzeniu Froyo działa to zarówno po zmianie orientacji telefonu, jak i podczas edycji elementu i powrotu do listy.


1
To rozwiązanie działało dla mnie. Inni nie. Jak zachowuje się zapisywanie / przywracanie stanu wystąpienia ListView w odniesieniu do usuwania i wstawiania elementów w ListView?
Bryan

2
Do Twojej wiadomości, jeśli użyjesz tego we fragmencie (korzystam z fragmentu listy) i przejdziesz do innych fragmentów, spowoduje to awarię, ponieważ widok zawartości nie jest tworzony podczas wywoływania mListState = getListView().onSaveInstanceState().głównie podczas obracania urządzenia.
Mgamerz

2
onRestoreInstanceStatenigdy nie nazywa się :(
dVaffection

1
@GiorgioBarchiesi Kim jest @ (Kirk Wool), którego rozwiązanie działa dla Ciebie? Użytkownik najwyraźniej zmienił swoje imię.
Piovezan

1
Tak, to oficjalna religia. Ale w moim przypadku dane są ładowane lokalnie, mają bardzo ograniczoną długość i ładują się w ułamku sekundy, więc poszedłem erethic :-)
Giorgio Barchiesi

26

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 jest w trybie dotykowym, pozycja zostanie faktycznie wybrana, jeśli w trybie dotykowym pozycja zostanie ustawiona 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);

2
w twojej odpowiedzi wystąpił błąd. Metoda nazywa się setSelection
Janusz

Pomysł działa dobrze, ale istnieje pseudo problem. Pierwsza widoczna pozycja może być pokazana tylko trochę (może tylko niewielka część wiersza), a setSelection () ustawia ten wiersz, aby był całkowicie widoczny, gdy przywracanie jest zrobiony.
rantravee

Proszę spojrzeć na moją drugą opcję. Właśnie dodałem ją do mojej pierwszej odpowiedzi
Francesco Laurita,

27
view.getScrollX () i view.getScrollY () zawsze zwracają 0!
rantravee

3
Spójrz na moje (zaakceptowane) rozwiązanie powyżej za pomocą View.getChildAt () i View.getTop (). Nie można używać View.getScrollY () na ListView, ponieważ ListView utrzymuje wewnętrzne przewijanie.
ian

17

Znalazłem w tym coś interesującego.

Próbowałem setSelection i scrolltoXY, ale to nie działało wcale, lista pozostała w tej samej pozycji, po kilku próbach i błędach otrzymał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 opublikowania Runnable spróbujesz runOnUiThread, to również nie działa (przynajmniej na niektórych urządzeniach)

Jest to bardzo dziwne obejście dla czegoś, co powinno być proste.


1
Wystąpił ten sam problem! I setSelectionFromTop()nie działa.
ofavre

+1. listnew.post (), nie działa na niektórych urządzeniach, a na niektórych innych urządzeniach nie działa w niektórych przypadkach, ale runOnUIThread działa dobrze.
Omar Rehman

@Omar, czy możesz podać przykłady urządzeń i systemów operacyjnych, na których to nie działa? Próbowałem HTC G2 (2.3.4) i Galaxy Nexus (4.1.2) i działało na obu. Żadna z pozostałych odpowiedzi w tym wątku nie działała na żadnym z moich urządzeń.
Dan J

2
listview.post działa dobrze na mojej Galaxy Note z 4.0.4, ale nie działa na moim Nexus One z 2.3.6. Jednak onOnUIThread (akcja) działa dobrze na obu urządzeniach.
Omar Rehman

11

UWAGA!! W AbsListView występuje błąd, który nie pozwala na prawidłowe działanie funkcji onSaveState (), jeśli ListView.getFirstVisiblePosition () ma wartość 0.

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

z 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, które uniemożliwiają prawidłowe przywrócenie stanu. Więc kiedy „góra” jest ujemna, zdobądź 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);

Działa jak urok, ale nie do końca go rozumiem. Jeśli pierwszy element jest nadal widoczny, jaki sens ma zdobycie następnego dziecka? Czy setSelectionFromTop z nowym indeksem nie powinien powodować wyświetlania listy od drugiego dziecka? Jak nadal widzę pierwszą?
tafi

@tafi, pierwszy element może być „częściowo” widoczny. Górna część tego elementu byłaby nad ekranem, a zatem byłaby liczbą ujemną. (Powoduje to inne problemy, których nie pamiętam ...) Więc używamy „góry” drugiego Przedmiotu do umieszczenia, który zawsze powinien być (?) Dostępny, inaczej nie będzie żadnego przewijania !
aaronvargas

6
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


Cześć, czy powyższy kod pomógłby mi z listą RecyclerView, w której stan instancji nie jest zapisywany między działaniami? Wysłałem tutaj następujące pytanie: stackoverflow.com/questions/35413495/... ?
AJW

6

W przypadku niektórych osób poszukujących rozwiązania tego problemu źródłem problemu może być ustawienie adaptera widoków list. Po ustawieniu adaptera w widoku listy resetuje on pozycję przewijania. Tylko coś do rozważenia. Przenieśliśmy ustawienie adaptera do mojego onCreateView po tym, jak złapaliśmy odwołanie do widoku listy, co rozwiązało problem. =)


1

Publikuję to, ponieważ jestem zaskoczony, że nikt o tym nie wspominał.

Po kliknięciu przycisku Wstecz użytkownik powróci do widoku listy w takim samym stanie, w jakim go opuścił.

Ten kod zastąpi przycisk „w górę”, aby zachowywać się tak samo jak przycisk Wstecz, więc w przypadku widoku listy -> Szczegóły -> Powrót do widoku listy (i żadnych innych opcji) jest to najprostszy kod do utrzymania pozycji przewijania i zawartości w widoku listy.

 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 innego działania z działania dotyczącego szczegółów, przycisk w górę spowoduje powrót do tego działania, więc będziesz musiał manipulować historią przycisku, aby to zadziałało.



1

NAJLEPSZE ROZWIĄZANIE TO:

// 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 WEZWAĆ W POSTIE I W GWINTU!


1

Możesz zachować stan przewijania po przeładowaniu, jeśli zapiszesz stan przed ponownym załadowaniem i przywrócisz po nim. W moim przypadku wysłałem asynchroniczne żądanie sieciowe i po zakończeniu przeładowałem listę w wywołaniu zwrotnym. To tutaj przywracam stan. Przykładowy kod to Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}

0

Jeśli używasz fragmentów hostowanych w działaniu, 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

Jeśli zapisujesz / przywracasz swoją pozycję przewijania ListView, zasadniczo kopiujesz funkcjonalność już zaimplementowaną w systemie Android. ListViewPrzywracanie porządku pozycja przewijania po prostu dobrze we własnym zakresie, z wyjątkiem jedno zastrzeżenie: jak @aaronvargas wspomniano tam jest błąd w AbsListViewże nie pozwoli, aby przywrócić wyśmienitą pozycję przewijania dla pierwszego elementu listy. Niemniej najlepszym sposobem na przywrócenie pozycji przewijania jest jej przywrócenie. System Android zrobi to lepiej dla Ciebie. Upewnij się, że spełniłeś następujące warunki:

  • upewnij się, że nie wywołałeś setSaveEnabled(false)metody i nie ustawiłeś android:saveEnabled="false"atrybutu dla listy w pliku układu xml
  • dla metody ExpandableListViewprzesłonięcia long getCombinedChildId(long groupId, long childId), tak aby zwracała dodatnią liczbę długą (domyślna implementacja w klasie BaseExpandableListAdapterzwraca 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 ListViewlub ExpandableListViewjest używany w fragmencie, nie odtwarzaj fragmentu podczas odtwarzania aktywności (na przykład po obróceniu ekranu). Uzyskaj fragment findFragmentByTag(String tag)metodą.
  • upewnij się, że ListViewma android:idi jest unikalny.

Aby uniknąć wyżej wspomnianego zastrzeżenia dotyczącego pierwszego elementu listy, możesz stworzyć adapter w sposób, w jaki zwraca on specjalny widok wysokości zerowego piksela dla ListViewpozycji 0. Oto prosty przykładowy projekt pokazuje ListViewi ExpandableListViewprzywraca ich dokładne pozycje przewijania, podczas gdy ich pozycje przewijania nie są jawnie zapisywane / przywrócone. Pozycja dokładnego przewijania jest przywracana idealnie nawet w przypadku złożonych scenariuszy z chwilowym przełączeniem na inną aplikację, podwójnym obrotem ekranu i powrotem do aplikacji testowej. Uwaga: jeśli wyraźnie wychodzisz z aplikacji (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

W przypadku działania pochodzącego z ListActivity, które implementuje LoaderManager.LoaderCallbacks za pomocą SimpleCursorAdapter, nie działało przywracanie pozycji w onReset (), ponieważ działanie prawie zawsze było restartowane, a adapter był ładowany ponownie, gdy widok szczegółów był zamknięty. Sztuką było przywrócenie pozycji w onLoadFinished ():

w 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

Żadne z zaproponowanych tutaj rozwiązań nie działało dla mnie. W moim przypadku mam ListVieww a, Fragmentktóre zastępuję w FragmentTransaction, więc nowa Fragmentinstancja jest tworzona za każdym razem, gdy fragment jest wyświetlany, co oznacza, że ListViewstanu nie można zapisać jako członkaFragment .

Zamiast tego zapisałem stan w mojej Applicationklasie niestandardowej . Poniższy kod powinien dać ci wyobrażenie o tym, 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 przechowywanie stanu poza instancją fragmentu. Jeśli nie podoba ci się pomysł posiadania pola statycznego w klasie aplikacji, myślę, że możesz to zrobić, implementując interfejs fragmentu i zapisując stan w swojej aktywności.

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

 

Ponadto, aby uniknąć „pozycji przewijania, która nie została zapisana, gdy widoczny jest pierwszy element”, można wyświetlić atrapę pierwszego elementu o 0pxwysokości. Można to osiągnąć, zastępując getView()adapter 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

Moja odpowiedź dotyczy bazy ogniowej, a pozycja 0 jest obejściem problemu

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 tę pracę tymczasowo, nie przeszkadzając użytkownikowi, mam nadzieję, że zadziała dla Ciebie


0

użyj tego kodu poniżej:

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

a przy każdym odświeżeniu danych użyj poniższego kodu:

adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);

0

Korzystam z FirebaseListAdapter i nie udało mi się uzyskać żadnego z rozwiązań. Skończyło się na tym. Sądzę, że są bardziej eleganckie sposoby, ale jest to kompletne i działające rozwiązanie.

Przed utworzeniem:

private int reset;
private int top;
private int index;

Wewnątrz 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();
    }
}

Ponieważ musiałem również rozwiązać ten problem dla FirebaseRecyclerAdapter , zamieszczam tutaj również rozwiązanie:

Przed utworzeniem:

private int reset;
private int top;
private int index;

Wewnątrz 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

Aby wyjaśnić doskonałą odpowiedź Ryana Newsoma i dostosować ją do fragmentów oraz do zwykłego przypadku, w którym chcemy nawigować z „głównego” fragmentu ListView do „szczegółowego” fragmentu, a następnie z powrotem do „głównego”

    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
    }

„Magia” polega na tym, że kiedy wracamy z fragmentu szczegółów do fragmentu ListView, widok nie jest odtwarzany, nie ustawiamy adaptera ListView, więc wszystko pozostaje tak, jak go zostawiliśmy!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.