Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Android

How can I save the scroll position for my ListView that uses a CursorAdapter?

My ListView (StickyListHeadersListView) does not save the exact scroll position. I referenced this:

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

When a user taps on an item in the list, I use SharedPreferences to save the "index" and "top" before the app transitions to an activity. When I press back on the activity to go back to the fragment with the ListView, the list goes to the top of the first visible list item from when it left, but not the exact position (i.e. if the first visible item is cut off halfway, it should be cut off halfway when returning to the fragment).

In other words, the offset is not saving. Like I said, it can save the first visible position, but the exact scroll is not being saved.

    mAdapter = new PeopleAdapter(getActivity(), null);
    mList = (StickyListHeadersListView) rootView.findViewById(R.id.stickyList);
    mList.setAdapter(mAdapter);
    mList.setAreHeadersSticky(true);
    mList.setDividerHeight(0);
    mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Cursor cursor = mAdapter.getCursor();
            if (cursor != null && cursor.moveToPosition(position)) {
                MainActivity.setIndex(mList.getFirstVisiblePosition());
                View v = mList.getListChildAt(0);
                int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
                MainActivity.setTop(top);
                Intent intent = new Intent(getActivity(), ContactDetailActivity.class);
                startActivity(intent);
        }
    });

After the cursor is done loading, I'm calling

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        android.util.Log.i(TAG, "Cursor loaded. (" + data.getCount() + " rows)");
        mList.setSelectionFromTop(MainActivity.getIndex(), MainActivity.getTop());
        mAdapter.changeCursor(data);
        mAdapter.notifyDataSetChanged();
}

Hey Michael!

Interesting question! I've only looked at restoring the top of a list item, like the problem you are having here.

This is something that I'll get back to you on in the next few days. I'll go ahead and make a test app here, try some things out and make sure it's definitely working before I post an answer.


Speak to you soon ;)

Awesome, thanks Harry! Let me know if you come up with something

1 Answer

Hey again Michael!

I've been looking into this over the past few days and I'm afraid I haven't found a suitable solution to this problem.


It's possible to use savedInstanceState to save the exact scroll of the ListView however, this is destroyed along with the activity under many circumstances and we can't easily persist the savedInstanceState as we can only really convert it into a Parcelable object, which isn't designed to be persisted. Even when trying to get the savedInstanceState from the ListView we have a few problems. null is often given back to us as the Android documentation specifies happens when there's "nothing interesting to save". Well, in this scenario, the scroll is pretty interesting! Sadly, Android isn't always going to give us this though, which brings a lot of problems...

In fact, I looked this up a bit more and noticed the Android documentation says "This state should only contain information that is not persistent or can not be reconstructed later. For example, you will never store your current position on screen because that will be computed again when a new instance of the view is placed in its view hierarchy.", so this is not the way to go.

Of course, we can still use the method you're using however, it will never be exact across all devices. For a general app, I would recommend just persisting the index of the list item and starting the user back at the top.

I don't believe there are any other methods we can use to access the scroll position, apart from getting the details from the ListView itself. Therefore, we can only use what we're given.

You could be super crazy mind, and try out putting a ListView in a ScrollView (Or some other containers would work too), where we can then use methods like getScrollY() and setScrollY() but this relies on there always being the same number of list items and all list items staying the same size.


Sorry I was unable to help! I've included some pointers above which may help you or other users in some scenarios but note that it won't be suitable for every scenario. If I ever get any other ideas on how to go about this though, I'll be sure to find this post again and write a new Comment :)

Thank you so much for your help Harry. I've spent hours upon hours trying to implement the ideas you posted (before you posted this), and none of them worked. At least I know why with your detailed explanation.

I posted this on stack overflow and nobody knew either, so perhaps there isn't an answer =P. I appreciate you putting the time into the answer!