web-dev-qa-db-de.com

Bildlaufposition beibehalten/speichern/wiederherstellen, wenn Sie zu einer ListView zurückkehren

Ich habe eine lange ListView, die der Benutzer durchblättern kann, bevor er zum vorherigen Bildschirm zurückkehrt. Wenn der Benutzer diese ListView-Datei erneut öffnet, möchte ich, dass die Liste an denselben Punkt verschoben wird, an dem sie zuvor war. Irgendwelche Ideen, wie man das erreichen kann?

376
rantravee

Versuche dies:

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

Erläuterung:

ListView.getFirstVisiblePosition() gibt den obersten sichtbaren Listeneintrag zurück. Dieses Element kann jedoch teilweise aus dem Bildlauf gescrollt werden. Wenn Sie die genaue Bildlaufposition der Liste wiederherstellen möchten, benötigen Sie diesen Versatz. ListView.getChildAt(0) gibt also den View für das oberste Listenelement zurück, und View.getTop() - mList.getPaddingTop() gibt dann seinen relativen Versatz vom oberen Rand des ListView zurück. Um die Scrollposition von ListView wiederherzustellen, rufen wir ListView.setSelectionFromTop() mit dem Index des gewünschten Elements und einem Versatz auf, um den oberen Rand des oberen Bereichs von ListView zu positionieren.

621
ian
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);
    }
}
524
Eugene Mymrin

Ich habe die von @ (Kirk Woll) vorgeschlagene Lösung angenommen, und sie funktioniert für mich. Ich habe auch im Android-Quellcode für die App "Kontakte" gesehen, dass sie eine ähnliche Technik verwenden. Ich möchte noch einige Details hinzufügen: Oben in meiner von ListActivity abgeleiteten Klasse:

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

Dann überschreibt eine Methode:

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

Natürlich ist "loadData" meine Funktion, um Daten aus der DB abzurufen und auf die Liste zu setzen.

Auf meinem Froyo-Gerät funktioniert dies sowohl, wenn Sie die Telefonausrichtung ändern, als auch wenn Sie ein Element bearbeiten und zur Liste zurückkehren.

51

Ein sehr einfacher Weg:

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

//Here u should save the currentPosition anywhere

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

Die Methode setSelection setzt die Liste auf den gelieferten Artikel zurück. Wenn Sie sich nicht im Touch-Modus befinden, wird das Element tatsächlich ausgewählt, wenn das Element im Touch-Modus nur auf dem Bildschirm positioniert wird.

Ein komplizierterer Ansatz:

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

Ich habe etwas Interessantes darüber gefunden.

Ich habe SetSelection und ScrolltoXY ausprobiert, aber es funktionierte überhaupt nicht. Die Liste blieb in der gleichen Position. Nach einigem Ausprobieren bekam ich den folgenden Code, der funktioniert

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

Wenn Sie RunOnUiThread anstelle von Runnable verwenden, funktioniert es auch nicht (zumindest auf einigen Geräten).

Dies ist eine sehr seltsame Lösung für etwas, das direkt sein sollte.

17
shalafi

VORSICHT!! Es gibt einen Fehler in AbsListView, durch den onSaveState () nicht ordnungsgemäß funktioniert, wenn ListView.getFirstVisiblePosition () 0 ist. 

Wenn Sie große Bilder haben, die den größten Teil des Bildschirms beanspruchen, und Sie zum zweiten Bild blättern, aber ein wenig des ersten angezeigt wird, wird die Bildlaufposition nicht gespeichert. 

from AbsListView.Java:1650 (kommentiert mein)

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

In diesem Fall ist das "Top" im nachstehenden Code jedoch eine negative Zahl, die andere Probleme verursacht, die eine korrekte Wiederherstellung des Status verhindern. Wenn also die "Spitze" negativ ist, holen Sie sich das nächste Kind

// 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
aaronvargas

Für einige, die nach einer Lösung für dieses Problem suchen, liegt der Grund des Problems möglicherweise darin, dass Sie den Listenansichtsadapter festlegen. Nachdem Sie den Adapter in der Listenansicht festgelegt haben, wird die Bildlaufposition zurückgesetzt. Nur etwas zu bedenken. Ich habe das Setzen des Adapters in mein onCreateView verschoben, nachdem wir den Verweis auf die Listenansicht genommen haben, und das Problem wurde für mich gelöst. =)

4
Ryan Newsom
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);
        }
    }
}

Das ist genug

4
Leo Phan

Ich poste dies, weil ich überrascht bin, dass niemand dies erwähnt hat.

Nachdem der Benutzer auf die Zurück-Schaltfläche geklickt hat, kehrt er in dem Zustand zurück, in dem er ihn verlassen hat. 

Dieser Code überschreibt die Aufwärtsschaltfläche, um sich wie die Zurückschaltfläche zu verhalten. Bei Listenansicht -> Details -> Zurück zur Listenansicht (und keine anderen Optionen) ist dies der einfachste Code, um die Bildlaufposition und den Inhalt beizubehalten in der Listenansicht.

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

Achtung: Wenn Sie aus der Detailaktivität zu einer anderen Aktivität wechseln können, kehren Sie mit der Aufwärts-Schaltfläche zu dieser Aktivität zurück. Sie müssen also den Backbutton-Verlauf bearbeiten, damit dies funktioniert. 

1
Mazvél

Sie können den Bildlaufstatus nach einem Neuladen beibehalten, wenn Sie den Zustand vor dem Neuladen speichern und danach wiederherstellen. In meinem Fall habe ich eine asynchrone Netzwerkanfrage gestellt und die Liste nach dem Abschluss erneut in einen Rückruf geladen. Hier stelle ich den Zustand wieder her. Codebeispiel ist Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

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

BESTE LÖSUNG IST:

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

DU MUSST IN POST UND IM GEWINN ANRUFEN!

1
Andrew Sneck

Ist Android:saveEnabled="true" in der ListView-XML-Deklaration nicht einfach genug?

1

benutze diesen Code:

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

und wann immer Sie Ihre Daten aktualisieren, verwenden Sie diesen folgenden Code:

adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
0
Rohit Lalwani

Bei einer von ListActivity abgeleiteten Aktivität, die LoaderManager.LoaderCallbacks mithilfe eines SimpleCursorAdapter implementiert, konnte die Position in onReset () nicht wiederhergestellt werden, da die Aktivität fast immer neu gestartet wurde und der Adapter beim Schließen der Detailansicht neu geladen wurde. Der Trick bestand darin, die Position in onLoadFinished () wiederherzustellen:

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

in onLoadFinished ():

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

in onBackPressed ():

// Show the top item at next start of the app
index = 0;
top = 0;
0
Perotin

Ich verwende FirebaseListAdapter und konnte keine der Lösungen zum Laufen bringen. Am Ende habe ich das getan. Ich schätze, es gibt elegantere Möglichkeiten, aber dies ist eine vollständige und funktionierende Lösung.

Vorher onCreate:

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

Innerhalb des 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++;
     }

 }

am Start:

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

Da ich dies auch für FirebaseRecyclerAdapter lösen musste, poste ich auch hier die Lösung:

Vorher onCreate:

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

Innerhalb des FirebaseRecyclerAdapters:

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

am Start:

@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
John T

Meine Antwort ist für Firebase und Position 0 ist ein Workaround

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

und convertView

position 0 und fügen Sie ein leeres Element hinzu, das Sie nicht verwenden

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

Ich habe diese Arbeit vorübergehend gesehen, ohne den Benutzer zu stören. Ich hoffe, es funktioniert für Sie

0
Trk

Wenn Sie selbst die Scrollposition von ListView speichern/wiederherstellen, duplizieren Sie im Wesentlichen die bereits im Android-Framework implementierte Funktionalität. Die ListView stellt die feine Scrollposition einfach selbst wieder her, mit einer Ausnahme: Wie @aaronvargas erwähnt, gibt es einen Fehler in AbsListView, der die feine Scrollposition für das erste Listenelement nicht wiederherstellen kann. Trotzdem ist es am besten, die Bildlaufposition nicht wiederherzustellen. Android Framework wird es für Sie besser machen. Stellen Sie nur sicher, dass Sie die folgenden Bedingungen erfüllen:

  • stellen Sie sicher, dass Sie die setSaveEnabled(false)-Methode nicht aufgerufen haben, und legen Sie das Android:saveEnabled="false"-Attribut für die Liste in der XML-Layoutdatei nicht fest
  • for ExpandableListView override long getCombinedChildId(long groupId, long childId)-Methode, sodass eine positive lange Zahl zurückgegeben wird (Standardimplementierung in der Klasse BaseExpandableListAdapter gibt eine negative Zahl zurück). Hier sind Beispiele:

.

@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;
}
  • wenn ListView oder ExpandableListView in einem Fragment verwendet wird, erstellen Sie das Fragment bei der Aktivitätswiederherstellung (z. B. nach einer Bildschirmdrehung) nicht neu. Fordern Sie das Fragment mit der findFragmentByTag(String tag)-Methode an.
  • stellen Sie sicher, dass die ListView einen Android:id hat und eindeutig ist.

Um den oben genannten Nachteil mit dem ersten Listenelement zu vermeiden, können Sie Ihren Adapter so gestalten, dass er für die ListView an Position 0 ..__ eine spezielle Dummy-Null-Pixel-Höhenansicht zurückgibt. Hier ist das einfache Beispielprojekt ListView und ExpandableListView Stellen Sie ihre feinen Bildlaufpositionen wieder her, während ihre Bildlaufpositionen nicht explizit gespeichert/wiederhergestellt werden. Die feine Bildlaufposition wird auch bei komplexen Szenarien mit temporärem Wechsel zu einer anderen Anwendung, doppelter Bildschirmrotation und Rückkehr zur Testanwendung perfekt wiederhergestellt. Wenn Sie die Anwendung explizit beenden (durch Drücken der Zurück-Taste), wird die Bildlaufposition nicht gespeichert (ebenso wie alle anderen Ansichten ihren Zustand nicht speichern) . https: // github .com/voromto/RestoreScrollPosition/Veröffentlichungen

0
Sergey

Keine der hier angebotenen Lösungen schien für mich zu funktionieren. In meinem Fall habe ich eine ListView in einer Fragment, die ich in einer FragmentTransaction ersetze. Daher wird jedes Mal, wenn das Fragment angezeigt wird, eine neue Fragment-Instanz erstellt. Dies bedeutet, dass der ListView-Status nicht als Member der Fragment-Datei gespeichert werden kann. .

Stattdessen habe ich den Zustand in meiner benutzerdefinierten Klasse Application gespeichert. Der folgende Code soll Ihnen eine Vorstellung davon vermitteln, wie das funktioniert:

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

}

Die Grundidee ist, dass Sie den Status außerhalb der Fragmentinstanz speichern. Wenn Ihnen die Idee, ein statisches Feld in Ihrer Anwendungsklasse zu haben, nicht gefällt, können Sie dies tun, indem Sie eine Fragmentschnittstelle implementieren und den Status in Ihrer Aktivität speichern.

Eine andere Lösung wäre, es in SharedPreferences zu speichern, aber es wird etwas komplizierter, und Sie müssen sicherstellen, dass Sie es beim Start der Anwendung löschen, es sei denn, Sie möchten, dass der Status auch zwischen App-Starts bestehen bleibt.

Um zu vermeiden, dass die Bildlaufposition nicht gespeichert wird, wenn das erste Objekt sichtbar ist, können Sie ein erstes Dummy-Objekt mit 0px height anzeigen. Dies kann erreicht werden, indem Sie getView() in Ihrem Adapter wie folgt überschreiben:

@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
Magnus W

Wenn Sie Fragmente verwenden, die für eine Aktivität gehostet werden, können Sie Folgendes tun:

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
saulobrito