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

后端 未结 20 1636
無奈伤痛
無奈伤痛 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:00

    I found something interesting about this.

    I tried setSelection and scrolltoXY but it did not work at all, the list remained in the same position, after some trial and error I got the following code that does work

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

    If instead of posting the Runnable you try runOnUiThread it does not work either (at least on some devices)

    This is a very strange workaround for something that should be straight forward.

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

    A very simple way:

    /** Save the position **/
    int currentPosition = listView.getFirstVisiblePosition();
    
    //Here u should save the currentPosition anywhere
    
    /** Restore the previus saved position **/
    listView.setSelection(savedPosition);
    

    The method setSelection will reset the list to the supplied item. If not in touch mode the item will actually be selected if in touch mode the item will only be positioned on screen.

    A more complicated approach:

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

    For an activity derived from ListActivity that implements LoaderManager.LoaderCallbacks using a SimpleCursorAdapter it did not work to restore the position in onReset(), because the activity was almost always restarted and the adapter was reloaded when the details view was closed. The trick was to restore the position 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;
    
    0 讨论(0)
  • 2020-11-21 22:04

    Try this:

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

    Explanation:

    ListView.getFirstVisiblePosition() returns the top visible list item. But this item may be partially scrolled out of view, and if you want to restore the exact scroll position of the list you need to get this offset. So ListView.getChildAt(0) returns the View for the top list item, and then View.getTop() - mList.getPaddingTop() returns its relative offset from the top of the ListView. Then, to restore the ListView's scroll position, we call ListView.setSelectionFromTop() with the index of the item we want and an offset to position its top edge from the top of the ListView.

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

    Neither of the solutions offered here seemed to work for me. In my case, I have a ListView in a Fragment which I'm replacing in a FragmentTransaction, so a new Fragment instance is created each time the fragment is shown, which means that the ListView state can not be stored as a member of the Fragment.

    Instead, I ended up storing the state in my custom Application class. The code below should give you an idea how this works:

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

    The basic idea is that you store the state outside the fragment instance. If you don't like the idea of having a static field in your application class, I guess you could do it by implementing a fragment interface and storing the state in your activity.

    Another solution would be to store it in SharedPreferences, but it gets a bit more complicated, and you would need to make sure you clear it on application launch unless you want the state to be persisted across app launches.

     

    Also, to avoid the "scroll position not saved when first item is visible", you can display a dummy first item with 0px height. This can be achieved by overriding getView() in your adapter, like this:

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

    use this below code :

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

    and whenever your refresh your data use this below code :

    adapter.notifyDataSetChanged();
    mList.setSelectionFromTop(index, top);
    
    0 讨论(0)
提交回复
热议问题