Question

J'ai une longue ListView que l'utilisateur peut faire défiler avant de revenir à l'écran précédent. Lorsque l'utilisateur ouvre ce ListView à nouveau, je veux que la liste soit défilée au même point qu'il était auparavant. Toutes les idées sur la façon d'y parvenir?

Était-ce utile?

La solution

Essayez ceci:

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

Explication:
retourne ListView.getFirstVisiblePosition() l'item de la liste visible en haut. Mais ce point peut être partiellement défilée hors de la vue, et si vous voulez restaurer la position exacte de défilement de la liste que vous avez besoin pour ce décalage. Alors ListView.getChildAt(0) retourne le View pour l'élément de liste de haut, et retourne ensuite View.getTop() - mList.getPaddingTop() son décalage par rapport à partir du haut de la ListView. Ensuite, pour restaurer la position de défilement du ListView, nous appelons ListView.setSelectionFromTop() avec l'index de l'élément que nous voulons et un décalage pour positionner son bord supérieur de la partie supérieure de la ListView.

Autres conseils

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

J'adopté la solution proposée par @ (Kirk Woll), et il fonctionne pour moi. Je l'ai aussi vu dans le code source Android pour l'application « Contacts », qu'ils utilisent une technique similaire. Je voudrais ajouter quelques détails: En plus de ma classe dérivée ListActivity-:

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

Ensuite, une méthode overrides:

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

Bien sûr « loadData » est ma fonction pour récupérer des données de la DB et le mettre sur la liste.

Sur mon appareil Froyo, cela fonctionne aussi bien lorsque vous modifiez l'orientation du téléphone, et lorsque vous modifiez un élément et revenir à la liste.

Une façon très simple:

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

//Here u should save the currentPosition anywhere

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

La méthode setSelection réinitialisera la liste à l'élément fourni. Dans le cas contraire en mode tactile l'élément sera effectivement sélectionné si en mode tactile l'élément ne sera positionné sur l'écran.

Une approche plus compliquée:

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

J'ai trouvé intéressant quelque chose à ce sujet.

J'ai essayé setSelection et scrolltoXY mais ça n'a pas du tout, la liste est restée dans la même position, après quelques essais et erreurs, je suis le code suivant qui fonctionne

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

Si au lieu d'afficher le Runnable vous essayez runOnUiThread il ne fonctionne pas non plus (au moins sur certains appareils)

Ceci est une solution très étrange pour quelque chose qui devrait être simple.

ATTENTION !! Il y a un bogue dans AbsListView qui ne permet pas le onSaveState () fonctionne correctement si le ListView.getFirstVisiblePosition () est 0.

Si vous avez de grandes images qui prennent la majeure partie de l'écran, et vous faites défiler vers la seconde image, mais un peu de la première montre, la position de défilement ne sera pas enregistré ...

de AbsListView.java:1650 (mes commentaires)

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

Mais dans cette situation, le « top » dans le code ci-dessous un certain nombre de négatif qui provoque d'autres problèmes qui empêchent l'Etat à restaurer correctement. Ainsi, lorsque le « top » est négative, obtenir le prochain enfant

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

Cela suffit

Pour une recherche d'une solution à ce problème, la racine du problème peut être là où vous configurez votre adaptateur vue sur la liste. Après avoir défini l'adaptateur sur le listview, il remet à zéro la position de défilement. Juste quelque chose à considérer. Je me suis déplacé Réglage de l'adaptateur dans mon onCreateView après que nous saisissons la référence au listview, et résolu le problème pour moi. =)

Am affichage parce que je suis surpris que personne l'avait mentionné.

Après l'utilisateur clique sur le bouton de retour, il retournera au listview dans le même état, comme il en sortit.

Ce code remplace le bouton « up » à se comporter de la même manière que le bouton de retour si dans le cas de Listview -> Détails -> Retour à Listview (et pas d'autres options) c'est le code le plus simple pour maintenir la scrollPosition et le contenu dans le listview.

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

Attention: Si vous pouvez passer à une autre activité de l'activité des détails sur le bouton jusqu'à vous revenir à cette activité de sorte que vous devrez manipuler l'histoire de backbutton pour que cela fonctionne.

est pas simplement android:saveEnabled="true" dans la déclaration assez xml ListView?

SOLUTION EST:

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

il faut appeler POST ET FILET!

Vous pouvez maintenir l'état de défilement après un rechargement si vous enregistrez l'état avant de recharger et de le restaurer après. Dans mon cas, je fait une demande de réseau asynchrone et rechargées la liste dans un rappel après terminé. C'est là que je reconstitue l'état. exemple de code est Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

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

Si vous utilisez des fragments hébergés sur une activité que vous pouvez faire quelque chose comme ceci:

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

Si vous enregistrez / restaurer la position de défilement de ListView vous vous êtes essentiellement dupliquant les fonctionnalités déjà mis en œuvre dans le cadre android. Le ListView restaure bien la position de défilement juste bien sur son propre sauf une mise en garde: comme @aaronvargas mentionné il y a un bogue dans AbsListView qui ne laisse pas de rétablir la position de défilement très bien pour le premier élément de la liste. Néanmoins, la meilleure façon de rétablir la position de défilement est de ne pas restaurer. cadre Android fera mieux pour vous. Assurez-vous que vous avez rempli les conditions suivantes:

  • assurez-vous que vous n'avez pas appelé méthode setSaveEnabled(false) et non mis attribut android:saveEnabled="false" pour la liste dans le fichier de configuration XML
  • pour la méthode ExpandableListView override long getCombinedChildId(long groupId, long childId) pour qu'il retourne le nombre longtemps positif (implémentation par défaut dans les déclarations de classe BaseExpandableListAdapter nombre négatif). Voici des exemples:

.

@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;
}
  • si ListView ou ExpandableListView est utilisé dans un fragment ne pas recréer le fragment sur les loisirs d'activité (après la rotation de l'écran par exemple). Obtenir le fragment avec la méthode findFragmentByTag(String tag).
  • assurez-vous que le ListView a une android:id et il est unique.

Pour éviter ladite mise en garde avec le premier élément de la liste, vous pouvez concevoir votre carte la façon dont il retourne factice spécial zéro pixel vue en hauteur pour la ListView à la position 0. Voici l'exemple simple montre de projet ListView et ExpandableListView restaurer les positions de leurs fins alors que leurs positions de défilement ne sont pas explicitement sauvegardées / restaurées. La position de fin est parfaitement restaurée, même pour les scénarios complexes avec commutation temporaire une autre application, double rotation de l'écran et le passage à l'application de test. S'il vous plaît noter, si vous quittez explicitement l'application (en appuyant sur la touche Back) la position de défilement ne sera pas sauvé (ainsi que toutes les autres vues ne sauveront pas leur état). https://github.com/voromto/RestoreScrollPosition/releases

Pour une activité dérivée de ListActivity qui implémente LoaderManager.LoaderCallbacks en utilisant un SimpleCursorAdapter il ne fonctionne pas pour rétablir la position dans onreset (), parce que l'activité a été presque toujours redémarré et l'adaptateur a été rechargé lorsque la vue de détails a été fermé. L'astuce était de restaurer la position dans onLoadFinished ():

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

dans onLoadFinished ():

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

dans onBackPressed ():

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

Aucune des solutions proposées ici ne semblait fonctionner pour moi. Dans mon cas, j'ai un ListView dans un Fragment que je suis remplaçant dans un FragmentTransaction, donc une nouvelle instance de Fragment est créé chaque fois que le fragment est montré, ce qui signifie que l'état de ListView ne peut pas être stockée en tant que membre du Fragment .

Au lieu de cela, je me suis retrouvé stocker l'état dans ma classe Application personnalisée. Le code ci-dessous devrait vous donner une idée comment cela fonctionne:

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'idée de base est que vous stockez l'état en dehors de l'instance de fragment. Si vous ne aimez pas l'idée d'avoir un champ statique dans votre classe d'application, je suppose que vous pouvez le faire en mettant en œuvre une interface de fragment et stocker l'état dans votre activité.

Une autre solution serait de le stocker dans SharedPreferences, mais il devient un peu plus compliqué, et vous devez videz sur le lancement de l'application, sauf si vous voulez que l'état soit persisté à travers le lancement de l'application.

En outre, pour éviter la « position de défilement pas enregistrée lorsque premier élément est visible », vous pouvez afficher un premier élément fictif à hauteur de 0px. Ceci peut être réalisé en remplaçant getView() dans votre adaptateur, comme ceci:

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

Ma réponse est pour Firebase et la position 0 est une solution de contournement

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

et convertView

Position 0 a ajouter un élément vide que vous n'utilisez pas

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

J'ai vu ce travail temporairement sans déranger l'utilisateur, je l'espère que cela fonctionne pour vous

utiliser ci-dessous le code suivant:

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

et chaque fois que votre rafraîchissement vos données ci-dessous utilisent ce code:

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

J'utilise FirebaseListAdapter et ne pouvait pas obtenir l'une des solutions au travail. J'ai fini par faire. Je suppose qu'il ya des façons plus élégantes, mais cela est une solution complète et de travail.

Avant onCreate:

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

À l'intérieur du 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();
    }
}

Depuis que je devais aussi résoudre ce FirebaseRecyclerAdapter Je suis de poster ici la solution pour cela aussi:

Avant onCreate:

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

À l'intérieur du 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();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top