Conserver / Enregistrer / Restaurer la position de défilement lors du retour à un ListView

397

J'ai un long temps ListViewque l'utilisateur peut faire défiler avant de revenir à l'écran précédent. Lorsque l'utilisateur l'ouvre à ListViewnouveau, je veux que la liste défile jusqu'au même point qu'elle était auparavant. Des idées sur la façon d'y parvenir?

rantravee
la source
22
Je pense que les solutions mentionnées par Eugene Mymrin / Giorgio Barchiesi sont meilleures que la réponse acceptée
Mira Weller

Réponses:

631

Essaye ça:

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

ListView.getFirstVisiblePosition()renvoie l'élément de liste visible le plus haut. Mais cet élément peut être partiellement défilé hors de vue, et si vous souhaitez restaurer la position de défilement exacte de la liste, vous devez obtenir ce décalage. Renvoie donc ListView.getChildAt(0)le Viewpour l'élément de liste supérieur, puis View.getTop() - mList.getPaddingTop()renvoie son décalage relatif par rapport au haut de ListView. Ensuite, pour restaurer la ListViewposition de défilement du, nous appelons ListView.setSelectionFromTop()avec l'index de l'élément que nous voulons et un décalage pour positionner son bord supérieur à partir du haut du ListView.

ian
la source
2
Je ne comprends pas vraiment comment cela fonctionne. J'utilise listView.getFirstVisiblePosition () uniquement et je comprends cela, mais je ne sais pas ce qui se passe ici. Une chance d'avoir une brève explication? :)
HXCaine
56
Ainsi, ListView.getFirstVisiblePosition () renvoie l'élément de liste visible en haut. Mais cet élément peut être partiellement défilé hors de vue, et si vous souhaitez restaurer la position de défilement exacte de la liste, vous devez obtenir ce décalage. ListView.getChildAt (0) renvoie donc la vue de l'élément de liste supérieur, puis View.getTop () renvoie son décalage relatif à partir du haut de ListView. Ensuite, pour restaurer la position de défilement de 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 à partir du haut de ListView. Tout est clair?
ian
15
Cela peut être encore plus facilement en utilisant une ligne pour sauver: int index = mList.getFirstVisiblePosition();et une seule ligne à restaurer: mList.setSelectionFromTop(index, 0);. Bonne réponse cependant (+1)! Je cherchais une solution élégante à ce problème.
Phil
17
@Phil, votre solution simpliste ne fonctionne pas pour restaurer la position de défilement exacte.
Ixx
2
comme l'a dit @nbarraille, le code devrait ressembler davantage à "v.getTop () - mList.getPaddingTop ()". Sinon, vous passerez une heure comme j'ai essayé de comprendre pourquoi il restaure toujours juste un cheveu ...
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
la source
106
Je pense que c'est la meilleure réponse car cette méthode restaure la position exacte du défilement et pas seulement le premier élément visible.
Mira Weller
9
Excellente réponse, mais ne fonctionne pas lors du chargement dynamique de contenu et vous souhaitez enregistrer l'état dans la méthode onPause ().
Gio
13
excellente solution. Un seul problème. Si les éléments sont volumineux et que vous faites défiler, mais que le premier élément est toujours visible, la liste revient en haut. si vous faites défiler jusqu'au deuxième élément ou n'importe où ailleurs, tout fonctionne
passsy
4
@passsy Avez-vous déjà trouvé une solution, la liste remontant en haut?
Papajohn000
2
Comme l'a dit aaronvargas, cela ne pouvait pas fonctionner lorsque ListView.getFirstVisiblePosition () était 0.
VinceStyling
54

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

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

Ensuite, certaines méthodes remplacent:

@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 les données de la base de données et les mettre dans la liste.

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

Giorgio Barchiesi
la source
1
Cette solution a fonctionné pour moi. Les autres non. Comment se comporte l'enregistrement / la restauration de l'état de l'instance ListView en ce qui concerne la suppression et l'insertion d'éléments dans ListView?
Bryan
2
Pour info si vous l'utilisez dans un fragment (j'utilise un fragment de liste) et naviguez vers d'autres fragments, cela plantera car la vue de contenu n'est pas créée lors de l'appel mListState = getListView().onSaveInstanceState().principalement sur la rotation de l'appareil.
Mgamerz
2
onRestoreInstanceStaten'est jamais appelé :(
dVaffection
1
@GiorgioBarchiesi Qui est @ (Kirk Wool) dont la solution fonctionne pour vous? L'utilisateur a apparemment changé de nom.
Piovezan
1
Oui, c'est la religion officielle. Mais dans mon cas, les données sont chargées localement, ont une longueur très limitée et se chargent en une fraction de seconde, alors je suis allé érétique :-)
Giorgio Barchiesi
26

Une manière 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éinitialise la liste à l'élément fourni. S'il n'est pas en mode tactile, l'élément sera réellement sélectionné si en mode tactile, l'élément sera uniquement positionné à 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);
Francesco Laurita
la source
2
il y avait une erreur dans votre réponse. La méthode est appelée setSelection
Janusz
L'idée fonctionne bien, mais il existe un pseudo problème: la première position visible peut ne s'afficher que légèrement (peut-être juste une petite partie de la ligne) et setSelection () définit cette ligne pour qu'elle soit complètement visible lorsque la restauration est terminée. fabriqué.
rantravee
Veuillez jeter un œil à ma deuxième option. Je viens de l'ajouter à ma première réponse
Francesco Laurita
27
view.getScrollX () et view.getScrollY () renvoient toujours 0!
rantravee
3
Jetez un œil à ma solution (acceptée) ci-dessus en utilisant View.getChildAt () et View.getTop (). Vous ne pouvez pas utiliser View.getScrollY () sur un ListView car un ListView maintient son défilement en interne.
ian
17

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

J'ai essayé setSelection et scrolltoXY mais cela n'a pas fonctionné du tout, la liste est restée dans la même position, après quelques essais et erreurs, j'ai obtenu 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 de publier Runnable, vous essayez runOnUiThread, cela ne fonctionne pas non plus (au moins sur certains appareils)

Il s'agit d'une solution de contournement très étrange pour quelque chose qui devrait être simple.

shalafi
la source
1
J'ai rencontré le même problème! Et setSelectionFromTop()ça ne marche pas.
ofavre
+1. listnew.post (), ne fonctionne pas sur certains appareils, et sur certains autres appareils cela ne fonctionne pas dans certains cas, mais runOnUIThread fonctionne très bien.
Omar Rehman
@Omar, pouvez-vous donner quelques exemples d'appareils et de systèmes d'exploitation sur lesquels cela ne fonctionne pas? J'ai essayé un HTC G2 (2.3.4) et un Galaxy Nexus (4.1.2) et cela a fonctionné sur les deux. Aucune des autres réponses de ce fil n'a fonctionné pour aucun de mes appareils.
Dan J
2
listview.post fonctionne bien sur mon Galaxy Note avec 4.0.4, mais ne fonctionne pas sur mon Nexus One avec 2.3.6. Cependant, onOnUIThread (action) fonctionne correctement sur les deux appareils.
Omar Rehman
11

MISE EN GARDE!! Il y a un bogue dans AbsListView qui ne permet pas à onSaveState () de fonctionner correctement si ListView.getFirstVisiblePosition () vaut 0.

Donc, si vous avez de grandes images qui occupent la majeure partie de l'écran et que vous faites défiler jusqu'à la deuxième image, mais qu'une petite partie de la première s'affiche, la position de défilement ne sera pas enregistrée ...

de AbsListView.java:1650 (commente le mien)

// 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 «haut» dans le code ci-dessous sera un nombre négatif qui provoque d'autres problèmes qui empêchent la restauration correcte de l'état. Donc, lorsque le «sommet» est négatif, obtenez 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);
aaronvargas
la source
Cela fonctionne comme un charme mais je ne le comprends pas complètement. Si le premier élément est toujours visible, comment est-il judicieux d'obtenir le prochain enfant? Le paramètre setSelectionFromTop avec le nouvel index ne doit-il pas entraîner l'affichage de la liste à partir du deuxième enfant? Comment est-ce que je vois toujours le premier?
tafi
@tafi, le premier élément pourrait être visible «partiellement». Ainsi, le haut de cet élément serait au-dessus de l'écran et serait donc un nombre négatif. (Cela provoque d'autres problèmes dont je ne me souviens pas ...) Nous utilisons donc le «haut» du deuxième élément pour le placement, qui devrait toujours (?) Être disponible, ou il n'y aurait pas de défilement en premier lieu !
aaronvargas
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);
        }
    }
}

C'est assez

Leo Phan
la source
Bonjour, votre code ci-dessus m'aiderait-il avec une liste RecyclerView où l'instancestate n'est pas enregistré entre les activités? J'ai posté la question suivante ici: stackoverflow.com/questions/35413495/… ?
AJW
6

Pour certains à la recherche d'une solution à ce problème, la racine du problème peut être l'endroit où vous définissez votre adaptateur de vues de liste. Une fois que vous avez défini l'adaptateur dans la liste, il réinitialise la position de défilement. Juste quelque chose à considérer. J'ai déplacé la configuration de l'adaptateur dans mon onCreateView après avoir récupéré la référence à la liste, et cela a résolu le problème pour moi. =)

Ryan Newsom
la source
1

Je poste cela parce que je suis surpris que personne n'en ait parlé.

Une fois que l'utilisateur a cliqué sur le bouton de retour, il revient à la vue de liste dans le même état qu'il en est sorti.

Ce code remplacera le bouton "haut" pour se comporter de la même manière que le bouton précédent, donc dans le cas de Listview -> Détails -> Retour à Listview (et pas d'autres options) c'est le code le plus simple pour maintenir la position de défilement et le contenu dans la liste.

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

Attention: Si vous pouvez accéder à une autre activité à partir de l'activité de détails, le bouton haut vous ramènera à cette activité, vous devrez donc manipuler l'historique du bouton de retour pour que cela fonctionne.

Mazvél
la source
1

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

VOUS DEVEZ APPELER EN POSTE ET EN FIL!

Andrew Sneck
la source
1

Vous pouvez conserver l'état de défilement après un rechargement si vous enregistrez l'état avant de le recharger et le restaurez après. Dans mon cas, j'ai fait une demande de réseau asynchrone et rechargé la liste dans un rappel une fois terminée. C'est là que je rétablis l'état. L'exemple de code est Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}
Michael Peterson
la source
0

Si vous utilisez des fragments hébergés sur une activité, 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());
          }
     }
}
saulobrito
la source
0

Si vous enregistrez / restaurez la position de défilement de ListViewvous - même, vous dupliquez essentiellement les fonctionnalités déjà implémentées dans le cadre Android. Le ListViewrestaure la position de défilement fin juste bien, sauf une mise en garde: comme l'a mentionné @aaronvargas, il y a un bug AbsListViewqui ne permet pas de restaurer la position de défilement fin pour le premier élément de la liste. Néanmoins, la meilleure façon de restaurer la position du défilement n'est pas de la restaurer. Le framework Android le fera mieux pour vous. Assurez-vous simplement que vous avez rempli les conditions suivantes:

  • assurez-vous que vous n'avez pas appelé de setSaveEnabled(false)méthode et que vous n'avez pas défini d' android:saveEnabled="false"attribut pour la liste dans le fichier de disposition xml
  • for ExpandableListViewoverride long getCombinedChildId(long groupId, long childId)method afin qu'il renvoie un nombre long positif (l'implémentation par défaut dans la classe BaseExpandableListAdapterrenvoie un nombre négatif). Voici quelques 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 ListViewou ExpandableListViewest utilisé dans un fragment, ne recréez pas le fragment lors de la recréation d'une activité (après la rotation de l'écran par exemple). Obtenez le fragment avec la findFragmentByTag(String tag)méthode.
  • assurez-vous que le ListViewa android:idet il est unique.

Pour éviter la mise en garde susmentionnée avec le premier élément de la liste, vous pouvez créer votre adaptateur de la manière dont il renvoie une vue fictive spéciale à zéro pixel pour la ListViewposition 0. Voici un exemple de projet simple qui montre ListViewet ExpandableListViewrestaure leurs positions de défilement fines alors que leurs positions de défilement ne sont pas explicitement enregistrées / restauré. La position de défilement fine est parfaitement restaurée, même pour les scénarios complexes, avec passage temporaire à une autre application, double rotation de l'écran et retour à l'application de test. Veuillez noter que si vous quittez explicitement l'application (en appuyant sur le bouton Retour), la position de défilement ne sera pas enregistrée (ainsi que toutes les autres vues n'enregistreront pas leur état). https://github.com/voromto/RestoreScrollPosition/releases

Sergey
la source
0

Pour une activité dérivée de ListActivity qui implémente LoaderManager.LoaderCallbacks à l'aide d'un SimpleCursorAdapter, il n'a pas fonctionné pour restaurer la position dans onReset (), car l'activité a presque toujours été redémarrée et l'adaptateur a été rechargé lorsque la vue des détails a été fermée. 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;
Perotin
la source
0

Aucune des solutions proposées ici ne semblait fonctionner pour moi. Dans mon cas, j'ai un ListViewdans un Fragmentque je remplace dans un FragmentTransaction, donc une nouvelle Fragmentinstance est créée chaque fois que le fragment est affiché, ce qui signifie que l' ListViewétat ne peut pas être stocké en tant que membre duFragment .

Au lieu de cela, j'ai fini par stocker l'état dans ma Applicationclasse personnalisée . Le code ci-dessous devrait vous donner une idée de 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 n'aimez pas l'idée d'avoir un champ statique dans votre classe d'application, je suppose que vous pouvez le faire en implémentant une interface de fragment et en stockant l'état dans votre activité.

Une autre solution serait de le stocker SharedPreferences, mais cela devient un peu plus compliqué, et vous devez vous assurer de l'effacer au lancement de l'application, sauf si vous souhaitez que l'état soit conservé pendant les lancements d'applications.

 

De plus, pour éviter la "position de défilement non enregistrée lorsque le premier élément est visible", vous pouvez afficher un premier élément factice avec une 0pxhauteur. Cela peut être réalisé en remplaçant getView()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);
}
Magnus W
la source
0

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 vierge 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, j'espère qu'il fonctionne pour vous

Trk
la source
0

utilisez ce code ci-dessous:

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 vous actualisez vos données, utilisez ce code ci-dessous:

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

J'utilise FirebaseListAdapter et je n'ai pu faire fonctionner aucune des solutions. J'ai fini par faire ça. Je suppose qu'il existe des moyens plus élégants, mais c'est une solution complète et fonctionnelle.

Avant onCreate:

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

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

Étant donné que j'ai également dû résoudre ce problème pour FirebaseRecyclerAdapter, je publie ici la solution pour cela également:

Avant onCreate:

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

À l'intérieur de 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();
    }
}
John T
la source
0

Pour clarifier l'excellente réponse de Ryan Newsom et l'ajuster pour les fragments et pour le cas habituel où nous voulons naviguer d'un fragment ListView "maître" à un fragment "détails" puis revenir au "maître"

    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
    }

La "magie" ici est que lorsque nous revenons du fragment de détails au fragment ListView, la vue n'est pas recréée, nous ne définissons pas l'adaptateur ListView, donc tout reste tel que nous l'avons laissé!

befstrat
la source