Domanda

Ho una lunga ListView che l'utente può scorrere in giro prima di tornare alla schermata precedente. Quando l'utente apre di nuovo questo ListView, voglio la lista a scorrere allo stesso punto che è stato in precedenza. Tutte le idee su come raggiungere questo obiettivo?

È stato utile?

Soluzione

Prova questo:

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

Spiegazione:
ListView.getFirstVisiblePosition() restituisce il primo elemento lista visibile. Ma questa voce può essere parzialmente scorrere fuori dalla vista, e se si desidera ripristinare l'esatta posizione di scorrimento della lista è necessario per ottenere questo offset. Così ListView.getChildAt(0) restituisce il View per la voce di elenco in alto, e ritorna quindi View.getTop() - mList.getPaddingTop() sua relativa compensati dalla parte superiore del ListView. Poi, per ripristinare la posizione di scorrimento del ListView, che noi chiamiamo ListView.setSelectionFromTop() con l'indice dell'elemento che vogliamo e un offset per posizionare il suo bordo superiore dalla parte superiore del ListView.

Altri suggerimenti

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

ho adottato la soluzione suggerita da @ (Kirk Woll), e funziona per me. Ho visto anche nel codice sorgente di Android per il "contatti" app, che usano una tecnica simile. Vorrei aggiungere qualche dettaglio in più: In cima alla mia classe ListActivity-derived:

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

Poi, alcune sostituzioni di metodo:

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

Naturalmente "loadData" è la mia funzione per recuperare i dati dal DB e metterlo sulla lista.

Il mio dispositivo Froyo, questo funziona sia quando si cambia l'orientamento del telefono, e quando si modifica un oggetto e tornare alla lista.

Un modo molto semplice:

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

//Here u should save the currentPosition anywhere

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

Il metodo setSelection sarà resettare l'elenco fino alla voce in dotazione. Se non in modalità touch voce verrà effettivamente selezionato se in modalità touch l'articolo sarà posizionato solo sullo schermo.

Un approccio più complicato:

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

Ho trovato qualcosa di interessante.

ho provato setSelection e scrolltoXY ma non ha funzionato affatto, la lista è rimasto nella stessa posizione, dopo alcuni tentativi ed errori ho ottenuto il seguente codice che funziona

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

Se invece di inviare il Runnable si tenta runOnUiThread non funziona né (almeno su alcuni dispositivi)

Questa è una soluzione molto strano per qualcosa che dovrebbe essere semplice.

ATTENZIONE !! C'è un bug in AbsListView che non consente l'onSaveState () per funzionare correttamente se il ListView.getFirstVisiblePosition () è 0.

Quindi, se si dispone di immagini di grandi dimensioni che occupano la maggior parte dello schermo, e si scorre la seconda immagine, ma un po 'della prima è visualizzato, la posizione di scorrimento non viene salvata ...

da AbsListView.java:1650 (commenti miniera)

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

Ma in questa situazione, il 'top' nel seguente codice sarà un numero negativo che provoca altri problemi che impediscono allo Stato di essere ripristinato correttamente. Così, quando il 'top' è negativo, ottenere il bambino successivo

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

Questo è sufficiente

Per alcuni alla ricerca di una soluzione a questo problema, la radice del problema può essere dove si sta impostando una visualizzazioni elenco adattatore. Dopo aver impostato l'adattatore sul ListView, si resetta la posizione di scorrimento. Solo una cosa da prendere in considerazione. Mi sono trasferito impostare l'adattatore nella mia onCreateView dopo abbiamo afferrare il riferimento alla ListView, e risolto il problema per me. =)

Am questo distacco perché sto sorpreso nessuno aveva parlato di questo.

Dopo utente fa clic sul pulsante indietro tornerà alla listview nello stesso stato in cui è andato fuori di esso.

Questo codice prevalgono sul pulsante "up" a comportarsi allo stesso modo il pulsante indietro così nel caso di Listview -> Dettagli -> Torna Listview (e altre opzioni) questo è il codice più semplice per mantenere la scrollPosition e il contenuto del ListView.

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

Attenzione: se si può andare a un'altra attività dall'attività dettagli il tasto su si ritornerà a tale attività in modo da avere a manipolare la storia BackButton in modo che questo lavoro.

Non è semplicemente android:saveEnabled="true" nel abbastanza dichiarazione di ListView xml?

BEST soluzione è:

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

è necessario chiamare in post ED IN FILO!

È possibile mantenere lo stato di scorrimento dopo una ricarica se si salva lo stato prima di ricaricare e ripristinarlo dopo. Nel mio caso ho fatto una richiesta di rete asincrona e ricaricato la lista in una richiamata dopo che è stato completato. Questo è dove ho ripristinare lo stato. Esempio di codice è Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

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

Se stai usando frammenti ospitati su un'attività che si può fare qualcosa di simile:

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

Se si sta salvando / ripristino posizione di scorrimento di ListView te si sono essenzialmente duplicando la funzionalità già implementata nel quadro Android. La posizione di scorrimento bene ripristini ListView solo bene da solo, tranne un avvertimento: come @aaronvargas menzionati c'è un bug in AbsListView che non ti consente di ripristinare posizione di scorrimento bene per la prima voce dell'elenco. Tuttavia il modo migliore per ripristinare posizione di scorrimento non è quello di ripristinarlo. framework Android farà meglio per voi. Basta fare in modo di aver soddisfatto le seguenti condizioni:

  • assicurarsi che non hai chiamato il metodo setSaveEnabled(false) e non impostare l'attributo android:saveEnabled="false" per l'elenco nel file di layout xml
  • per il metodo di ExpandableListView long getCombinedChildId(long groupId, long childId) di sostituzione in modo che restituisca il numero lungo positivo (implementazione di default in classe BaseExpandableListAdapter rendimenti numero negativo). Ecco alcuni esempi:

.

@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;
}
  • se ListView o ExpandableListView viene utilizzato in un frammento non ricreare il frammento sull'attività ricreazione (dopo la rotazione dello schermo per esempio). Ottenere il frammento con il metodo findFragmentByTag(String tag).
  • assicurarsi che il ListView ha un android:id ed è unico nel suo genere.

Per evitare caveat di cui sopra con il primo elemento della lista si può creare la scheda il modo in cui ritorna speciale manichino zero pixel vista l'altezza per il ListView alla posizione 0. Ecco il semplice esempio mostra del progetto ListView e ExpandableListView ripristinare posizioni di loro fini di scorrimento mentre le loro posizioni di scorrimento non sono esplicitamente salvate / restaurato. posizione fine di scorrimento viene ripristinato perfettamente anche per i complessi scenari con il passaggio temporaneo a qualche altra applicazione, la rotazione doppio schermo e di nuovo passare alla applicazione di test. Si prega di notare, se si sta uscendo in modo esplicito l'applicazione (premendo il pulsante Indietro) la posizione di scorrimento non verrà salvata (così come tutte le altre viste non salverà il loro stato). https://github.com/voromto/RestoreScrollPosition/releases

Per un'attività valere ListActivity che implementa LoaderManager.LoaderCallbacks utilizzando un SimpleCursorAdapter non ha funzionato per ripristinare la posizione in onreset (), perché l'attività è stata quasi sempre riavviato e l'adattatore è stato ricaricato quando la visualizzazione dati è stato chiuso. Il trucco era quello di ripristinare la posizione in onLoadFinished ():

in onListItemClick ():

// save the selected item position when an item was clicked
// to open the details
index = getListView().getFirstVisiblePosition();
View v = getListView().getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - getListView().getPaddingTop());

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;

Nessuna delle soluzioni proposte qui sembrava funzionare per me. Nel mio caso, ho un ListView in un Fragment che sto sostituendo in un FragmentTransaction, quindi una nuova istanza Fragment viene creata ogni volta viene mostrato il frammento, che significa che lo stato ListView non può essere memorizzato come un membro della Fragment .

Invece, ho finito per la memorizzazione dello stato nella mia classe Application personalizzato. Il seguente codice dovrebbe darvi un'idea di come funziona:

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

}

L'idea di base è che si memorizza lo stato di fuori l'istanza frammento. Se non ti piace l'idea di avere un campo statico nella classe di applicazione, penso che si potrebbe farlo mediante l'attuazione di un'interfaccia frammento e la memorizzazione dello stato nella vostra attività.

Un'altra soluzione potrebbe essere quella di conservarlo in SharedPreferences, ma diventa un po 'più complicato, e si avrebbe bisogno per essere sicuri di cancellare sul lancio di applicazioni a meno che non si desidera che la condizione per essere mantenuta attraverso lanci di app.

Inoltre, per evitare la "posizione di scorrimento non salvata quando primo elemento è visibile", è possibile visualizzare un manichino primo elemento con altezza 0px. Ciò può essere ottenuto mediante l'override getView() l'adattatore, in questo modo:

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

La mia risposta è per Firebase e la posizione 0 è una soluzione

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

e convertView

posizione 0 a aggiungere un elemento vuoto che non si sta utilizzando

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

ho visto questo lavoro temporaneo senza disturbare l'utente, spero che funziona per voi

utilizzare questo codice qui sotto:

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

e ogni volta che l'aggiornamento dei dati utilizzare questo codice qui sotto:

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

sto usando FirebaseListAdapter e non sono riuscito a ottenere una qualsiasi delle soluzioni per il lavoro. Ho finito per fare questo. Sto indovinando ci sono modi più eleganti, ma questa è una soluzione completa e funzionante.

Prima onCreate:

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

All'interno della 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 quando ho avuto anche per risolvere questo per FirebaseRecyclerAdapter sto postando la soluzione qui anche per questo:

Prima onCreate:

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

All'interno della 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();
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top