Frage

habe ich eine lange ListView, dass der Benutzer zum vorherigen Bildschirm scrollen kann vor der Rückkehr. Wenn der Benutzer wieder dieses ListView öffnet, möchte ich die Liste an der gleichen Stelle gescrollt werden, dass es vorher war. Alle Ideen, wie dies zu erreichen?

War es hilfreich?

Lösung

Versuchen Sie diese:

// 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() kehrt das obere sichtbare Listenelement. Aber dieser Artikel teilweise Sicht gescrollt werden kann, und wenn Sie die genaue Bildlaufposition der Liste wiederherstellen möchten, müssen Sie dies versetzt werden. So ListView.getChildAt(0) die View für die Top-Liste Element zurückgibt, und dann View.getTop() - mList.getPaddingTop() kehrt seine relative von der Spitze des ListView versetzt. Dann wiederherzustellen die Scroll-Position des ListView, wir ListView.setSelectionFromTop() mit dem Index des Elements nennen wir wollen und einen Versatz der Oberkante des ListView von oben zu positionieren.

Andere Tipps

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

Ich nahm die Lösung von @ (Kirk Woll) vorgeschlagen, und es funktioniert für mich. Ich habe auch in der Android-Quellcode für die „Kontakte“ app, dass sie eine ähnliche Technik verwenden gesehen. Ich möchte noch einige weitere Details hinzuzufügen: Oben auf meiner ListActivity abgeleitete Klasse:

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

Dann einige Methode überschreibt:

@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 „loaddata“ ist meine Funktion Daten aus der DB abrufen und auf die Liste gesetzt.

Auf meinem Froyo Gerät, das funktioniert, sowohl wenn Sie das Telefon Ausrichtung ändern, und wenn Sie ein Element bearbeiten und in die Liste zurück.

Eine sehr einfache Art und Weise:

/** 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 wird zurückgesetzt die Liste der Liefergegenstand. Wenn nicht in Berührung Modus wird das Element tatsächlich, wenn in Kontakt-Modus ausgewählt werden wird das Element nur auf dem Bildschirm positioniert werden.

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

fand ich etwas interessantes dieses.

Ich habe versucht, setSelection und scrolltoXY aber es hat nicht funktioniert überhaupt blieb die Liste in der gleichen Position, nach einigem Versuch und Irrtum habe ich den folgenden Code, das funktioniert

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

Wenn stattdessen die Runnable der Entsendung Sie runOnUiThread versuchen, es nicht funktioniert entweder (zumindest auf einigen Geräten)

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

ACHTUNG !! Es ist ein Fehler in AbsListView, die nicht den onSaveState () erlauben ordnungsgemäß zu funktionieren, wenn die ListView.getFirstVisiblePosition () 0 ist

Wenn Sie also große Bilder haben, die fast den gesamten Bildschirm einnehmen, und blättern Sie zu dem zweiten Bild, aber ein wenig von den ersten zeigen, wird die Scrollposition nicht gespeichert werden ...

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

Aber in dieser Situation ist die ‚top‘ in dem folgenden Code wird eine negative Zahl sein, die anderen Probleme verursacht, die den Zustand verhindert korrekt gestellt werden. Also, wenn die ‚top‘ negativ ist, bekommt 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);
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

Für einige die Suche nach einer Lösung für dieses Problem kann die Wurzel des Problems sein, wo Sie Ihre Listenansichten Adapter setzen. Nachdem Sie den Adapter auf dem Listview gesetzt ist, setzt sie die Bildlaufposition. Nur etwas zu prüfen. Ich zog den Adapter in meine onCreateView Einstellung, nachdem wir den Verweis auf die Listenansicht greifen, und es löste das Problem für mich. =)

Am diesem Posting, weil ich bin überrascht, niemand dies erwähnt hatte.

Nach dem Benutzer die Zurück-Schaltfläche klickt er auf die Listenansicht im gleichen Zustand zurück, als er davon ausging.

Dieser Code wird die „nach oben“ Taste außer Kraft setzt auf die gleiche Weise wie die Zurück-Taste, um im Fall der Listenansicht zu verhalten -> Details -> zurück zur Listenansicht (und keine andere Optionen) ist dies der einfachste Code die scroll zu halten und der Inhalt 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 auf eine andere Tätigkeit aus der Details Aktivität gehen kann die oben-Taste gelangen Sie wieder zurück zu dieser Tätigkeit, so dass Sie die Geschichte in Backbutton, um für diese zu arbeiten haben zu manipulieren.

Ist das nicht einfach in der Listview-XML-Deklaration genug android:saveEnabled="true"?

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 POST UND GEWINDE ANRUF!

Sie können den Scroll-Zustand nach einem reload pflegen, wenn Sie speichern den Zustand, bevor Sie neu zu laden und es wieder nach. In meinem Fall habe ich eine asynchrone Netzwerkanforderung und nachgeladen die Liste in einem Rückruf, nachdem sie abgeschlossen. Dies ist, wo ich Zustand wiederherstellen. Codebeispiel ist Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

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

Wenn Sie auf eine Aktivität gehostet Fragmente verwenden Sie so etwas wie dies tun können:

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

Wenn Sie speichern / wiederherstellen Scrollposition von ListView selbst Sie duplizieren im wesentlichen die Funktionalität bereits in Android-Framework implementiert. Die ListView Wiederherstellungen feine Scroll-Position nur gut auf seinem eigenen, außer einer Einschränkung: als @aaronvargas erwähnte es ein Fehler in AbsListView ist, die nicht in Ordnung Scroll-Position für das erste Listenelement wiederherstellen lassen. Dennoch ist die beste Art und Weise Scroll-Position wiederherzustellen, ist es nicht wiederhergestellt werden. Android Framework wird für Sie es besser machen. So stellen Sie sicher, dass Sie die folgenden Bedingungen erfüllt sind:

  • stellen Sie sicher, Sie haben nicht setSaveEnabled(false) Methode und nicht gesetzt android:saveEnabled="false" Attribut für die Liste in der XML-Layout-Datei
  • genannt
  • für ExpandableListView Überschreibung long getCombinedChildId(long groupId, long childId) Methode, so dass es positive lange Nummer (Default-Implementierung in der Klasse BaseExpandableListAdapter kehrt negative Zahl) zurück. Hier sind 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 ListView oder ExpandableListView in einem Fragmente verwendet wird, kann das Fragment auf Aktivität Erholung nicht neu (nach Bildschirmdrehung zum Beispiel). Rufen Sie das Fragment mit findFragmentByTag(String tag) Methode.
  • sicherstellen, dass der ListView eine android:id hat, und es ist einzigartig.

Um vorgenannten Vorbehalt mit dem ersten Listenelement vermeiden Sie Ihren Adapter die Art und Weise Handwerk kann es spezielle Dummy-Null Pixel Höhe Ansicht für die ListView 0 an Position zurückkehrt. Hier ist die einfache Beispielprojekt zeigt ListView und ExpandableListView ihre feinen Scroll-Positionen wiederherzustellen, während ihre Scroll-Positionen werden nicht explizit gespeichert / wiederhergestellt. Feine Bildlaufposition ist perfekt auch für die komplexen Szenarien mit temporärer Umschaltung auf eine anderen Anwendung, Doppelbildschirmdrehung gestellt und Zurückschalten auf die Testanwendung. Bitte beachten Sie, wenn Sie explizit die Anwendung verlässt (indem Sie die Taste Zurück drücken) die Scrollposition nicht (wie auch alle anderen Ansichten speichern ihren Zustand nicht) gespeichert werden. https://github.com/voromto/RestoreScrollPosition/releases

Für eine Aktivität von ListActivity abgeleitet, dass Geräte LoaderManager.LoaderCallbacks eine SimpleCursorAdapter ihn nicht die Position in onreset wiederherzustellen funktionierte (), weil die Aktivität wurde fast immer neu gestartet und der Adapter neu geladen wurde, wenn die Details Ansicht geschlossen wurde. Der Trick war, um 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;

Keine der angebotenen Lösungen hier schien für mich an der Arbeit. In meinem Fall habe ich eine ListView in einem Fragment, die ich in einem FragmentTransaction bin zu ersetzen, so dass eine neue Fragment Instanz jedes Mal erstellt wird, das Fragment gezeigt wird, was bedeutet, dass der ListView Zustand nicht als Mitglied des Fragment gespeichert werden kann .

Stattdessen landete ich den Zustand, in meiner benutzerdefinierten Application Klasse Speicherung von bis. Der folgende Code sollte Ihnen eine Idee geben, 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 Zustand außerhalb des Fragments Instanz speichern. Wenn Sie ein statisches Feld mit in Ihrer Anwendungsklasse nicht wie die Idee zu tun, ich denke, man es durch die Implementierung eines Schnittstelle Fragment tun könnte und den Zustand in Ihrer Aktivität zu speichern.

Eine andere Lösung wäre es in SharedPreferences zu speichern, aber es wird ein wenig komplizierter, und Sie würden sicher, dass Sie es beim Programmstart deaktivieren vornehmen müssen, wenn Sie den Zustand wollen über App startet beibehalten werden.

Auch die „Scroll-Position nicht gespeichert, wenn erstes Element ist sichtbar“ zu vermeiden, können Sie ein Dummy erstes Element mit 0px Höhe anzuzeigen. Dies kann durch zwingenden getView() in Ihrem Adapter erreicht werden, wie folgt:

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

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

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 eines 0 ein leeres Element hinzufügen, dass 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

Mit diesem Code unten:

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 wenn Ihre refresh Ihre Daten verwenden, um dies unter Code:

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

Ich bin mit FirebaseListAdapter und konnte keine der Lösungen an die Arbeit. Ich landete diese auf zu tun. Ich vermute, es gibt elegantere Möglichkeiten, aber das 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++;
     }

 }

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

Da ich auch dies hatte zu lösen für FirebaseRecyclerAdapter Ich poste hier die Lösung für diese auch:

Vor onCreate:

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

Innerhalb des 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();
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top