Behalten Sie die Bildlaufposition bei, speichern Sie sie / stellen Sie sie wieder her, wenn Sie zu einer ListView zurückkehren

397

Ich habe eine lange Zeit ListView, die der Benutzer scrollen kann, bevor er zum vorherigen Bildschirm zurückkehrt. Wenn der Benutzer dies ListViewerneut öffnet , soll die Liste an den gleichen Punkt gescrollt werden, an dem sie zuvor war. Irgendwelche Ideen, wie dies erreicht werden kann?

Rantravee
quelle
22
Ich denke, die von Eugene Mymrin / Giorgio Barchiesi erwähnten Lösungen sind besser als die akzeptierte Antwort
Mira Weller

Antworten:

631

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 das oberste sichtbare Listenelement zurück. Dieses Element wird jedoch möglicherweise teilweise nicht mehr angezeigt. Wenn Sie die genaue Bildlaufposition der Liste wiederherstellen möchten, müssen Sie diesen Versatz abrufen. So ListView.getChildAt(0)gibt das Viewfür das Top-Listenelement, und dann View.getTop() - mList.getPaddingTop()kehrt die relative von der Spitze des Offset ListView. Um die Bildlaufposition des Elements wiederherzustellen ListView, rufen wir ListView.setSelectionFromTop()mit dem Index des gewünschten Elements und einem Versatz auf, um dessen Oberkante von der Oberseite des Elements zu positionieren ListView.

Ian
quelle
2
Ich verstehe nicht wirklich, wie das funktioniert. Ich verwende nur listView.getFirstVisiblePosition () und verstehe das, bin mir aber nicht sicher, was hier los ist. Gibt es eine Chance auf eine kurze Erklärung? :)
HXCaine
56
ListView.getFirstVisiblePosition () gibt also das oberste sichtbare Listenelement zurück. Dieses Element wird jedoch möglicherweise teilweise nicht mehr angezeigt. Wenn Sie die genaue Bildlaufposition der Liste wiederherstellen möchten, müssen Sie diesen Versatz abrufen. ListView.getChildAt (0) gibt also die Ansicht für das oberste Listenelement zurück, und View.getTop () gibt den relativen Versatz vom oberen Rand der ListView zurück. Um die Bildlaufposition der ListView wiederherzustellen, rufen wir ListView.setSelectionFromTop () mit dem Index des gewünschten Elements und einem Versatz auf, um dessen obere Kante vom oberen Rand der ListView zu positionieren. Alles klar?
ian
15
Dies kann noch einfacher gemacht werden, indem eine Zeile zum Speichern verwendet wird: int index = mList.getFirstVisiblePosition();und nur eine Zeile zum Wiederherstellen : mList.setSelectionFromTop(index, 0);. Tolle Antwort (+1)! Ich habe nach einer eleganten Lösung für dieses Problem gesucht.
Phil
17
@Phil Ihre vereinfachte Lösung funktioniert nicht, um die genaue Bildlaufposition wiederherzustellen.
Ixx
2
Wie @nbarraille sagte, sollte der Code eher wie "v.getTop () - mList.getPaddingTop ()" lauten. Andernfalls verbringen Sie eine Stunde wie ich damit, herauszufinden, warum immer nur ein Haar wiederhergestellt wird ...
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);
    }
}
Eugene Mymrin
quelle
106
Ich denke, dies ist die beste Antwort, da diese Methode die genaue Bildlaufposition und nicht nur das erste sichtbare Element wiederherstellt.
Mira Weller
9
Gute Antwort, funktioniert aber nicht, wenn Inhalte dynamisch geladen werden und Sie den Status in der onPause () -Methode speichern möchten.
Gio
13
tolle Lösung. Nur ein Problem. Wenn die Elemente groß sind und Sie scrollen, das erste Element jedoch weiterhin sichtbar ist, springt die Liste wieder nach oben. Wenn Sie zum zweiten Element oder irgendwo anders scrollen, funktioniert alles
passsy
4
@passsy Hast du jemals eine Lösung gefunden, bei der die Liste wieder nach oben springt?
Papajohn000
2
Wie aaronvargas sagte, konnte dies nicht funktionieren, wenn ListView.getFirstVisiblePosition () 0 ist.
VinceStyling
54

Ich habe die von @ (Kirk Woll) vorgeschlagene Lösung übernommen und sie funktioniert für mich. Ich habe auch im Android-Quellcode für die "Kontakte" -App 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 Datenbank abzurufen und in die Liste aufzunehmen.

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

Giorgio Barchiesi
quelle
1
Diese Lösung hat bei mir funktioniert. Die anderen taten es nicht. Wie verhält sich das Speichern / Wiederherstellen des ListView-Instanzstatus beim Löschen und Einfügen von Elementen in ListView?
Bryan
2
Zu Ihrer Information: Wenn Sie dies in einem Fragment verwenden (ich verwende ein Listenfragment) und zu anderen Fragmenten navigieren, stürzt dies ab, da die Inhaltsansicht beim Aufruf von mListState = getListView().onSaveInstanceState().Hauptsächlich bei Gerätedrehung nicht erstellt wird.
Mgamerz
2
onRestoreInstanceStatewird nie genannt :(
dVaffection
1
@GiorgioBarchiesi Wer ist @ (Kirk Wool), dessen Lösung für Sie funktioniert? Der Benutzer hat anscheinend seinen Namen geändert.
Piovezan
1
Ja, das ist die offizielle Religion. Aber in meinem Fall werden die Daten lokal geladen, haben eine sehr begrenzte Länge und werden in Sekundenbruchteilen geladen, also bin ich erethisch geworden :-)
Giorgio Barchiesi
26

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 das angegebene Element zurück. Wenn sich das Objekt nicht im Touch-Modus befindet, wird es tatsächlich ausgewählt, wenn es sich im Touch-Modus nur auf dem Bildschirm befindet.

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);
Francesco Laurita
quelle
2
In Ihrer Antwort ist ein Fehler aufgetreten. Die Methode heißt setSelection
Janusz
Die Idee funktioniert gut, aber es gibt ein Pseudoproblem. Die erste sichtbare Position wird möglicherweise nur ein wenig angezeigt (möglicherweise nur ein kleiner Teil der Zeile), und setSelection () setzt diese Zeile so, dass sie bei der Wiederherstellung vollständig sichtbar ist gemacht.
Rantravee
Bitte werfen Sie einen Blick auf meine zweite Option. Ich habe es gerade zu meiner ersten Antwort hinzugefügt
Francesco Laurita
27
view.getScrollX () und view.getScrollY () geben immer 0 zurück!
Rantravee
3
Schauen Sie sich meine (akzeptierte) Lösung oben mit View.getChildAt () und View.getTop () an. Sie können View.getScrollY () nicht in einer ListView verwenden, da eine ListView den internen Bildlauf beibehält.
ian
17

Ich fand etwas Interessantes daran.

Ich habe setSelection und scrolltoXY ausprobiert, aber es hat überhaupt nicht funktioniert. Die Liste blieb an derselben Position. Nach einigem Ausprobieren habe ich den folgenden Code erhalten, der funktioniert

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

Wenn Sie statt Runnable runOnUiThread veröffentlichen, funktioniert dies auch nicht (zumindest auf einigen Geräten).

Dies ist eine sehr seltsame Problemumgehung für etwas, das einfach sein sollte.

Shalafi
quelle
1
Ich habe das gleiche Problem erlebt! Und setSelectionFromTop()funktioniert nicht.
ofavre
+1. listnew.post () funktioniert auf einigen Geräten nicht und auf anderen Geräten in bestimmten Fällen nicht, aber runOnUIThread funktioniert einwandfrei.
Omar Rehman
@Omar, können Sie einige Beispiele für Geräte und Betriebssysteme nennen, auf denen dies nicht funktioniert? Ich habe ein HTC G2 (2.3.4) und ein Galaxy Nexus (4.1.2) ausprobiert und es hat auf beiden funktioniert. Keine der anderen Antworten in diesem Thread funktionierte für eines meiner Geräte.
Dan J
2
listview.post funktioniert auf meinem Galaxy Note mit 4.0.4 einwandfrei, auf meinem Nexus One mit 2.3.6 jedoch nicht. OnOnUIThread (Aktion) funktioniert jedoch auf beiden Geräten einwandfrei.
Omar Rehman
11

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

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

von AbsListView.java:1650 (Kommentare von mir)

// 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 dieser Situation ist das 'Top' im folgenden Code jedoch eine negative Zahl, was zu anderen Problemen führt, die verhindern, dass der Status korrekt wiederhergestellt wird. 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);
aaronvargas
quelle
Es funktioniert wie ein Zauber, aber ich verstehe es nicht ganz. Wenn das erste Element noch sichtbar ist, wie ist es sinnvoll, das nächste Kind zu bekommen? Sollte setSelectionFromTop mit dem neuen Index nicht dazu führen, dass die Liste ab dem zweiten untergeordneten Element angezeigt wird? Wie sehe ich noch den ersten?
Tafi
@tafi, Das erste Element könnte "teilweise" sichtbar sein. Der obere Rand dieses Elements befindet sich also über dem Bildschirm und ist daher eine negative Zahl. (Dies führt zu anderen Problemen, an die ich mich nicht erinnere ...) Wir verwenden also das 'Top' des zweiten Elements für die Platzierung, das immer (?) Verfügbar sein sollte, oder es würde überhaupt kein Scrollen geben !
Aaronargas
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);
        }
    }
}

Das ist genug

Leo Phan
quelle
Hallo, würde Ihr obiger Code mir bei einer RecyclerView-Liste helfen, in der instancestate zwischen den Aktivitäten nicht gespeichert wird? Ich habe hier die folgende Frage gestellt: stackoverflow.com/questions/35413495/… ?
AJW
6

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

Ryan Newsom
quelle
1

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

Nachdem der Benutzer auf die Schaltfläche "Zurück" geklickt hat, kehrt er in demselben Zustand zur Listenansicht zurück, in dem er sie verlassen hat.

Dieser Code überschreibt die Schaltfläche "Auf" und verhält sich genauso wie die Schaltfläche "Zurück". Im Fall von Listenansicht -> Details -> Zurück zur Listenansicht (und ohne weitere 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 über die Detailaktivität zu einer anderen Aktivität wechseln können, kehren Sie mit der Aufwärts-Schaltfläche zu dieser Aktivität zurück, sodass Sie den Backbutton-Verlauf bearbeiten müssen, damit dies funktioniert.

Mazvél
quelle
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);
   }
});

SIE MÜSSEN NACH DER POST UND IM GEWINDE ANRUFEN!

Andrew Sneck
quelle
1

Sie können den Bildlaufstatus nach einem Neuladen beibehalten, wenn Sie den Status vor dem Neuladen speichern und danach wiederherstellen. In meinem Fall habe ich eine asynchrone Netzwerkanforderung gestellt und die Liste nach Abschluss in einem Rückruf neu 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)
}
Michael Peterson
quelle
0

Wenn Sie Fragmente verwenden, die in einer 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());
          }
     }
}
Saulobrito
quelle
0

Wenn Sie die Bildlaufposition von sich ListViewselbst speichern / wiederherstellen, duplizieren Sie im Wesentlichen die bereits im Android Framework implementierte Funktionalität. Das ListViewstellt die Position des feinen Bildlaufs nur für sich allein wieder her, abgesehen von einer Einschränkung: Wie @aaronvargas erwähnte, gibt es einen Fehler AbsListView, der es nicht ermöglicht, die Position des feinen Bildlaufs für das erste Listenelement wiederherzustellen. Der beste Weg, um die Bildlaufposition wiederherzustellen, besteht jedoch nicht darin, sie wiederherzustellen. Android Framework wird es besser für Sie tun. Stellen Sie einfach sicher, dass Sie die folgenden Bedingungen erfüllt haben:

  • Stellen Sie sicher, dass Sie die setSaveEnabled(false)Methode nicht aufgerufen und kein android:saveEnabled="false"Attribut für die Liste in der XML-Layoutdatei festgelegt haben
  • für die ExpandableListViewÜberschreibungsmethode long getCombinedChildId(long groupId, long childId), sodass eine positive lange Zahl zurückgegeben wird (die Standardimplementierung in der Klasse BaseExpandableListAdaptergibt eine negative Zahl zurück). Hier einige 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 ListViewoder ExpandableListViewin einem Fragment verwendet wird, erstellen Sie das Fragment bei der Neuerstellung der Aktivität nicht neu (z. B. nach Bildschirmdrehung). Erhalten Sie das Fragment mit findFragmentByTag(String tag)Methode.
  • Stellen Sie sicher, dass das ListVieweine hat android:idund es einzigartig ist.

Um die oben genannten Einschränkungen beim ersten Listenelement zu vermeiden, können Sie Ihren Adapter so gestalten, dass er eine spezielle Dummy-Nullpixel-Höhenansicht für die ListViewPosition 0 zurückgibt. In diesem einfachen Beispielprojekt werden die feinen Bildlaufpositionen angezeigt ListViewund ExpandableListViewwiederhergestellt, während die Bildlaufpositionen nicht explizit gespeichert werden /restauriert. Die Position des feinen Bildlaufs wird auch in komplexen Szenarien mit vorübergehendem Wechsel zu einer anderen Anwendung, doppelter Bildschirmdrehung und Zurückschalten zur Testanwendung perfekt wiederhergestellt. Bitte beachten Sie, dass beim expliziten Beenden der Anwendung (durch Drücken der Zurück-Taste) die Bildlaufposition nicht gespeichert wird (und alle anderen Ansichten ihren Status nicht speichern). https://github.com/voromto/RestoreScrollPosition/releases

Sergey
quelle
0

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 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;
Perotin
quelle
0

Keine der hier angebotenen Lösungen schien für mich zu funktionieren. In meinem Fall habe ich ein ListViewin einem, Fragmentdas ich in einem ersetze FragmentTransaction, sodass Fragmentjedes Mal, wenn das Fragment angezeigt wird, eine neue Instanz erstellt wird, was bedeutet, dass der ListViewStatus nicht als Mitglied des gespeichert werden kann Fragment.

Stattdessen habe ich den Status in meiner benutzerdefinierten ApplicationKlasse gespeichert . Der folgende Code soll Ihnen eine Vorstellung davon geben, wie dies 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 wahrscheinlich tun, indem Sie eine Fragmentschnittstelle implementieren und den Status in Ihrer Aktivität speichern.

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

 

Um zu vermeiden, dass die Bildlaufposition nicht gespeichert wird, wenn das erste Element sichtbar ist, können Sie ein erstes Dummy-Element mit der 0pxHöhe anzeigen . Dies kann erreicht werden, indem Sie getView()Ihren 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);
}
Magnus W.
quelle
0

Meine Antwort ist für Firebase und Position 0 ist eine Problemumgehung

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 a 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, sie funktioniert für Sie

Trk
quelle
0

Verwenden Sie diesen folgenden 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 den folgenden Code:

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

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

Vor 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 die Lösung auch hier:

Vor onCreate:

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

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

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();
    }
}
John T.
quelle
0

Um die ausgezeichnete Antwort von Ryan Newsom zu verdeutlichen und für Fragmente anzupassen und für den üblichen Fall, dass wir von einem "Master" -ListView-Fragment zu einem "Detail" -Fragment und dann zurück zum "Master" navigieren möchten.

    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
    }

Die "Magie" hier ist, dass wenn wir vom Detailfragment zurück zum ListView-Fragment navigieren, die Ansicht nicht neu erstellt wird, wir den ListView-Adapter nicht einstellen, sodass alles so bleibt, wie wir es verlassen haben!

befstrat
quelle