Maintain/Save/Restore scroll position when returning to a ListView

后端 未结 20 1682
無奈伤痛
無奈伤痛 2020-11-21 21:30

I have a long ListView that the user can scroll around before returning to the previous screen. When the user opens this ListView again, I want the

相关标签:
20条回答
  • 2020-11-21 22:10

    I'm using FirebaseListAdapter and couldn't get any of the solutions to work. I ended up doing this. I'm guessing there are more elegant ways but this is a complete and working solution.

    Before onCreate:

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

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

    Since I also had to solve this for FirebaseRecyclerAdapter I'm posting the solution here for that too:

    Before onCreate:

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

    Inside of the 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();
        }
    }
    
    0 讨论(0)
  • 2020-11-21 22:11

    CAUTION!! There is a bug in AbsListView that doesn't allow the onSaveState() to work correctly if the ListView.getFirstVisiblePosition() is 0.

    So If you have large images that take up most of the screen, and you scroll to the second image, but a little of the first is showing, the scroll position Won't be saved...

    from AbsListView.java:1650 (comments mine)

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

    But in this situation, the 'top' in the code below will be a negative number which causes other issues that prevent the state to be restored correctly. So when the 'top' is negative, get the next child

    // 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);
    
    0 讨论(0)
  • 2020-11-21 22:16

    You can maintain the scroll state after a reload if you save the state before you reload and restore it after. In my case I made a asynchronous network request and reloaded the list in a callback after it completed. This is where I restore state. Code sample is Kotlin.

    val state = myList.layoutManager.onSaveInstanceState()
    
    getNewThings() { newThings: List<Thing> ->
    
        myList.adapter.things = newThings
        myList.layoutManager.onRestoreInstanceState(state)
    }
    
    0 讨论(0)
  • 2020-11-21 22:19

    For some looking for a solution to this problem, the root of the issue may be where you are setting your list views adapter. After you set the adapter on the listview, it resets the scroll position. Just something to consider. I moved setting the adapter into my onCreateView after we grab the reference to the listview, and it solved the problem for me. =)

    0 讨论(0)
  • 2020-11-21 22:19

    If you're using fragments hosted on an activity you can do something like this:

    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());
              }
         }
    }
    
    0 讨论(0)
  • 2020-11-21 22:20

    My answer is for Firebase and position 0 is a workaround

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

    and convertView

    position 0 a add a blank item that you are not using

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

    I have seen this work temporarily without disturbing the user, I hope it works for you

    0 讨论(0)
提交回复
热议问题