2012-08-31 19 views
7

Después de pasar algunos días investigando este problema, finalmente me rindo y publico esta pregunta. Preguntas similares han sido respondidas aquí (en parte) y se propusieron soluciones, pero ninguna de ellas finalmente me ayudó. La diferencia con respecto a los temas tratados y mi problema parece ser: no de los otros parece usar una SimpleCursorTreeAdapter para alimentar a la lista ...Guardar y restaurar el estado contraído de ExpandableListActivity con SimpleCursorTreeAdapter

Problema: Cuando se cambia la orientación de mi dispositivo (Samsung Galaxy S), el estado de expansión de los grupos no se preserva y restaura. Todos los grupos aparecen cerrados y la lista se desplaza hacia la parte superior.

El problema ya está visible en el Android API demos. En caso de que los tenga instalados, vaya a:

Vistas -> Listas ampliables -> 2. Cursor (Personas): inicie la muestra, expanda cualquier grupo, gire su dispositivo y el resultado muestra el grupo en estado contraído .

En el siguiente código, hice lo siguiente: Tomé el código de la demostración de API (ExpandableList2.java) y lo extendí con implementaciones para onSaveInstantState(), onRestoreInstanceState() y onResume(). Las implementaciones para estos métodos se tomaron de otro hilo de discusión aquí (How to preserve scroll position in an ExpandableListView).

import android.app.ExpandableListActivity; 
import android.content.AsyncQueryHandler; 
import android.content.ContentUris; 
import android.content.Context; 
import android.database.Cursor; 
import android.net.Uri; 
import android.os.Bundle; 
import android.os.Parcelable; 
import android.provider.ContactsContract.CommonDataKinds.Phone; 
import android.provider.ContactsContract.Contacts; 
import android.view.View; 
import android.widget.CursorTreeAdapter; 
import android.widget.ExpandableListView; 
import android.widget.SimpleCursorTreeAdapter; 

public class MainActivity extends ExpandableListActivity { 

private static final String LIST_STATE_KEY = "levelSelectListState"; 
private static final String LIST_POSITION_KEY = "levelSelectListPosition"; 
private static final String ITEM_POSITION_KEY = "levelSelectItemPosition"; 

private static final String[] CONTACTS_PROJECTION = new String[] { 
     Contacts._ID, Contacts.DISPLAY_NAME }; 
private static final int GROUP_ID_COLUMN_INDEX = 0; 

private static final String[] PHONE_NUMBER_PROJECTION = new String[] { 
     Phone._ID, Phone.NUMBER }; 

private static final int TOKEN_GROUP = 0; 
private static final int TOKEN_CHILD = 1; 

private static final class QueryHandler extends AsyncQueryHandler { 
    private CursorTreeAdapter mAdapter; 

    public QueryHandler(Context context, CursorTreeAdapter adapter) { 
     super(context.getContentResolver()); 
     this.mAdapter = adapter; 
    } 

    @Override 
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 
     switch (token) { 
     case TOKEN_GROUP: 
      mAdapter.setGroupCursor(cursor); 
      break; 

     case TOKEN_CHILD: 
      int groupPosition = (Integer) cookie; 
      mAdapter.setChildrenCursor(groupPosition, cursor); 
      break; 
     } 
    } 
} 

public class MyExpandableListAdapter extends SimpleCursorTreeAdapter { 

    // Note that the constructor does not take a Cursor. This is done to 
    // avoid querying the 
    // database on the main thread. 
    public MyExpandableListAdapter(Context context, int groupLayout, 
      int childLayout, String[] groupFrom, int[] groupTo, 
      String[] childrenFrom, int[] childrenTo) { 

     super(context, null, groupLayout, groupFrom, groupTo, childLayout, 
       childrenFrom, childrenTo); 
    } 

    @Override 
    protected Cursor getChildrenCursor(Cursor groupCursor) { 
     // Given the group, we return a cursor for all the children within 
     // that group 

     // Return a cursor that points to this contact's phone numbers 
     Uri.Builder builder = Contacts.CONTENT_URI.buildUpon(); 
     ContentUris.appendId(builder, 
       groupCursor.getLong(GROUP_ID_COLUMN_INDEX)); 
     builder.appendEncodedPath(Contacts.Data.CONTENT_DIRECTORY); 
     Uri phoneNumbersUri = builder.build(); 

     mQueryHandler.startQuery(TOKEN_CHILD, groupCursor.getPosition(), 
       phoneNumbersUri, PHONE_NUMBER_PROJECTION, Phone.MIMETYPE 
         + "=?", new String[] { Phone.CONTENT_ITEM_TYPE }, 
       null); 

     return null; 
    } 
} 

private QueryHandler mQueryHandler; 
private CursorTreeAdapter mAdapter; 
private Parcelable listState; 
private int listPosition; 
private int itemPosition; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    // Set up our adapter 
    mAdapter = new MyExpandableListAdapter(
      this, 
      android.R.layout.simple_expandable_list_item_1, 
      android.R.layout.simple_expandable_list_item_1, 
      new String[] { Contacts.DISPLAY_NAME }, // Name for group 
                // layouts 
      new int[] { android.R.id.text1 }, 
      new String[] { Phone.NUMBER }, // Number for child layouts 
      new int[] { android.R.id.text1 }); 

    setListAdapter(mAdapter); 

    mQueryHandler = new QueryHandler(this, mAdapter); 

    // Query for people 
    mQueryHandler.startQuery(TOKEN_GROUP, null, Contacts.CONTENT_URI, 
      CONTACTS_PROJECTION, Contacts.HAS_PHONE_NUMBER + "=1", null, 
      null); 
} 

@Override 
protected void onDestroy() { 
    super.onDestroy(); 

    // Null out the group cursor. This will cause the group cursor and all 
    // of the child cursors 
    // to be closed. 
    mAdapter.changeCursor(null); 
    mAdapter = null; 
} 

@Override 
protected void onSaveInstanceState(Bundle outState) { 
    super.onSaveInstanceState(outState); 

    ExpandableListView listView = this.getExpandableListView(); 
    listState = listView.onSaveInstanceState(); 
    outState.putParcelable(LIST_STATE_KEY, listState); 

    listPosition = listView.getFirstVisiblePosition(); 
    outState.putInt(LIST_POSITION_KEY, listPosition); 

    View itemView = listView.getChildAt(0); 
    itemPosition = itemView == null ? 0 : itemView.getTop(); 
    outState.putInt(ITEM_POSITION_KEY, itemPosition); 
} 

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

    listState = state.getParcelable(LIST_STATE_KEY); 
    listPosition = state.getInt(LIST_POSITION_KEY); 
    itemPosition = state.getInt(ITEM_POSITION_KEY); 
} 

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

    ExpandableListView listView = this.getExpandableListView(); 
    if (listView != null) { 
     if (listState != null) { 
      // yes, this code is reached 
      listView.onRestoreInstanceState(listState); 
      listView.setSelectionFromTop(listPosition, itemPosition); 
     } 
    } 
} 
} 

Con el depurador Me aseguré, todos estos métodos se llama en realidad en el orden correcto cuando se cambia la orientación del dispositivo. Sin embargo: Restaurar el estado no tiene efecto.

Además depuración en ella, he encontrado que el contenido de la restaurada listState en onRestoreInstanceState() contiene una matriz vacía, mientras que el listState en onSaveInstanceState() contiene datos en su conjunto cuando se ponen en la Outstate.

Así que mi suposición es que el listState parcelable no se conserva o se recupera correctamente por el sistema ...

¿Alguien puede señalar lo que está mal, o proporcionar un ejemplo de trabajo mediante el SimpleCursorTreeAdapter con "trabajo" restaurar de expand/collapsed state?

(Nota: Para reproducir este problema con el código de la actividad anterior, es necesario proporcionar el permiso android.permission.READ_CONTACTS)

+2

¡Esta es una pregunta bien formada! – giZm0

+0

Después de depurar este problema varias veces en diferentes versiones de Android, renuncio a la solución directa. Finalmente no pude averiguar por qué ** listState ** no se llena correctamente en onRestoreInstanceState(). Entonces, ahora estoy usando la solución explícita descrita en http://stackoverflow.com/questions/4184556/save-and-restore-expanded-collapsed-state-of-an-expandablelistactivity Esto funciona bien para mi problema . – Michael

Respuesta

0

esto podría dibujar desdén por parte de algunos de nuestros programadores más escrupulosos, pero ¿ha intentado hacer simplemente ¿sus estados contienen un objeto static? Pero incluso antes de eso, tal vez podría beneficiarse de agregar un notifydatasetchanged() en algún lugar después de recuperar su objeto.

Cuestiones relacionadas