13

in my app I have a class derived from ExpandableListActivity. When I scroll the contents, and then change phone orientation or edit an item and then go back to the list, the original list position is not preserved. I have tried two solutions, similar to those I have recently successfully used with ListActivity-derived classes.

Solution 1:

protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    mListState = state.getParcelable(LIST_STATE);
}

protected void onResume() {
    super.onResume();
    loadData();
    if (mListState != null)
        getExpandableListView().onRestoreInstanceState(mListState);
}

protected void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    mListState = getExpandableListView().onSaveInstanceState();
    state.putParcelable(LIST_STATE, mListState);
}

Solution 2:

protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    mListPosition = state.getInt(LIST_STATE);
}

protected void onResume() {
    super.onResume();
    loadData();
    getExpandableListView().setSelection(mListPosition);
}

protected void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    mListPosition = getExpandableListView().getFirstVisiblePosition();
    state.putInt(LIST_STATE, mListPosition);
}

Neither solution works. I have also tried to combine the two solutions, and this works when I edit an item and go back to the list, but it does NOT work when I change the phone orientation

Anyone can suggest a good way to achieve my goal?

Giorgio Barchiesi
  • 6,109
  • 3
  • 32
  • 36
  • Sorry, I have fixed a coding error and it seems the two solutions combined work correctly. I was using the same key (i.e. LIST_STATE) both for saving the state and the position. – Giorgio Barchiesi Apr 19 '11 at 08:49
  • Related: [Maintain/Save/Restore scroll position when returning to a ListView](http://stackoverflow.com/q/3014089) – blahdiblah Dec 20 '12 at 20:47

4 Answers4

22

After a few experiments I have come to a satisfactory solution, which also preserves the fine scroll position of the top visible item.

As a matter of fact, three different pieces of information need to be saved and restored: the list state (e.g. which groups are expanded), the index of the first visible item, and its fine scroll position.

Unfortunately, it seems that only the first one is saved by the onSaveInstanceState method of the expandable list view, so the other two need to be saved separately. This is different from a non expandable list view, where it appears the onSaveInstanceState method saves all the information needed to properly restore state and position of the list (on this subject, see Maintain/Save/Restore scroll position when returning to a ListView).

Here are the code snippets; on top of the ExpandableListActivity-derived class:

private static final String LIST_STATE_KEY = "listState";
private static final String LIST_POSITION_KEY = "listPosition";
private static final String ITEM_POSITION_KEY = "itemPosition";

private Parcelable mListState = null;
private int mListPosition = 0;
private int mItemPosition = 0;

Then, some overrides:

protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);

    // Retrieve list state and list/item positions
    mListState = state.getParcelable(LIST_STATE_KEY);
    mListPosition = state.getInt(LIST_POSITION_KEY);
    mItemPosition = state.getInt(ITEM_POSITION_KEY);
}

protected void onResume() {
    super.onResume();

    // Load data from DB and put it onto the list
    loadData();

    // Restore list state and list/item positions
    ExpandableListView listView = getExpandableListView();
    if (mListState != null)
        listView.onRestoreInstanceState(mListState);
    listView.setSelectionFromTop(mListPosition, mItemPosition);
}

protected void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);

    // Save list state
    ExpandableListView listView = getExpandableListView();
    mListState = listView.onSaveInstanceState();
    state.putParcelable(LIST_STATE_KEY, mListState);

    // Save position of first visible item
    mListPosition = listView.getFirstVisiblePosition();
    state.putInt(LIST_POSITION_KEY, mListPosition);

    // Save scroll position of item
    View itemView = listView.getChildAt(0);
    mItemPosition = itemView == null ? 0 : itemView.getTop();
    state.putInt(ITEM_POSITION_KEY, mItemPosition);
}

This works fine on my Froyo device.

Community
  • 1
  • 1
Giorgio Barchiesi
  • 6,109
  • 3
  • 32
  • 36
  • "Unfortunately, it seems that only the first one is saved by the onSaveInstanceState method of the expandable list view, so the other two need to be saved separately." This is not true. You just have to call onRestoreInstanceState() after setting the adapter (because setting an adapter resets the current scroll position). That is, only using onSave...() and onRestore...() will remember also the current scroll position. – Zsolt Safrany Feb 07 '13 at 17:11
  • 1
    As a variation on this, if you are working with an ExpandableListView within a fragment, you can retrieve your list state and position from the savedInstanceState in onCreateView. Also, make sure you do not have setRetainInstance(true) set, or the savedInstanceState will always be null. – alice_silver_man Sep 06 '14 at 02:50
7

This is how I got ExpandableListView.onRestoreInstanceState and onSaveInstanceState to work...

If you're using BaseExpandableListAdapter the default implementation of getCombinedChildId returns a negative number. If you look into the code for restoring a list from saved state scroll position it ignores ids which are negative. Return a positive IDs so that the AbsListView onRestoreInstanceState will set the scroll position properly. To return positive IDs change the or of getCombinedChildId from 0x8000000000000000L (negative) to 0x7000000000000000L (positive) by over-riding the method in your subclass of BaseExpandableListAdapter as follows...

        public long getCombinedChildId(long groupId, long childId)
        {
            long or  = 0x7000000000000000L;
            long group = (groupId & 0x7FFFFFFF) << 32;
            long child = childId & 0xFFFFFFFF;
            return or | group | child;
        }
Cal
  • 1,625
  • 4
  • 20
  • 30
0

I found another way to do that works for me to maintain state. Here is the link.

http://markmail.org/message/msesxums77bdoqnx#query:+page:1+mid:utmart4ob7flh3u7+state:results

The summary is that you always have to set state, even if the convertView != null.

I don't know the repercussions of always setting the state, but in my case it works perfectly.

jhamm
  • 24,124
  • 39
  • 105
  • 179
-2

To prevent a reload of your activity when changing phone orientation, simply add the following line to your AndroidManifest.xml in the activity section:

        android:configChanges="keyboardHidden|orientation|screenSize" 
Marcel Verwey
  • 441
  • 5
  • 6