¿Cómo mantener la selección de la casilla de verificación en el adaptador del cursor al cambiar el cursor?

Tengo un ListView que estoy llenando con información de la tienda de medios. Tengo casillas de verificación en cada fila para permitir al usuario seleccionar varias filas. En el menú de opciones, hay elementos de menú que activan nuevas consultas en el almacén de medios y, al utilizar un CursorLoader, intercambio el cursor en el adaptador cuando este se ha cargado.

En mi adaptador estoy usando un ArrayList que realiza un seguimiento de los elementos marcados y uno que realiza un seguimiento de todos los elementos en la lista. La lista que realiza un seguimiento de todos los elementos debe ser reordenada / reconstruida cuando el cursor cambia para permitir que el método getView () marque las casillas de verificación correctas.No he podido encontrar una manera de actualizar mi lista para que se corresponda con el orden de los elementos en el cursor.

He intentado anular swapCursor, changeCursor y notifyDataSetChanged. En cada intento de llamar al súper método antes y después de recurrir a la lista como se hace en el constructor del adaptador.

He visto estos problemas aquí en SO que parecen estar relacionados pero no he podido crear una solución:Problemas con el adaptador Listview

Estados de CursorAdapter y CheckBox personalizados

El cursor no vincula el texto correctamente con un adaptador personalizado

Este es mi código de actividad:

/**
 * This activity displays a list of the available media on the device. It allows
 * selecting several items from the list and by selecting the "done" icon in the
 * options menu, the activity will return the results to the calling activity.
 * 
 * The list can be sorted via the options menu. The available sorting columns
 * are artist, title and album. By default the list is sorted by artist name.
 * 
 * The selection from the database consists of the _ID, ARTIST, ALBUM, TITLE,
 * DATA, DISPLAY_NAME and DURATION columns and is also limited to contain only
 * files that are markes as IS_MUSIC.
 * 
 * @author Daniel Kvist
 * 
 */
public class MediaSelectorActivity extends Activity implements LoaderCallbacks<Cursor>
{
    private static final int LOADER_ID_ARTIST = 2;
    private static final int LOADER_ID_ALBUM = 4;
    private static final int LOADER_ID_TITLE = 8;

    public static final String EXTRA_SELECTED_ITEMS = "selected_media";
    public static final int REQUEST_MEDIA = 0;

    private MediaSelectorAdapter adapter;
    private ListView listView;
    private LoaderManager loaderManager;

    private String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0";
    private String[] projection = { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
            MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.DURATION };
    private ArrayList<Track> selectedItems;

    /**
     * The onCreate method loads the xml layout which contains the listview. It
     * also gets the loader manager and initiates a first load of available
     * media and sorts it by artist name.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_selector);
        loaderManager = getLoaderManager();
        loaderManager.initLoader(LOADER_ID_ARTIST, null, this);
        listView = (ListView) findViewById(R.id.list);
        selectedItems = new ArrayList<Track>();
    }

    /**
     * This method simply inflates the xml file which contains the menu options.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.media_selector_menu, menu);
        return true;
    }

    /**
     * This is called when an option item has been selected. Depending on the
     * user selection either the selected tracks are passed back to the calling
     * activity or a new query is made to the media store to sort on either
     * artist, album or title.
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        selectedItems = adapter.getSelectedItems();
        switch (item.getItemId())
        {
            case R.id.done:
                Intent intent = new Intent();
                intent.putParcelableArrayListExtra(EXTRA_SELECTED_ITEMS, selectedItems);
                setResult(RESULT_OK, intent);
                finish();
                return true;
            case R.id.artist:
                loaderManager.initLoader(LOADER_ID_ARTIST, null, this);
                return true;
            case R.id.album:
                loaderManager.initLoader(LOADER_ID_ALBUM, null, this);
                return true;
            case R.id.track:
                loaderManager.initLoader(LOADER_ID_TITLE, null, this);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Called when the cursor loader is first created. It decides which URI to
     * query and which sorting order should be returned. The query also contains
     * information about which columns we are interested in which selection we
     * want.
     */
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle)
    {
        CursorLoader cursorLoader = null;
        switch (i)
        {
            case LOADER_ID_ARTIST:
                cursorLoader = new CursorLoader(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
                        MediaStore.Audio.Media.ARTIST);
                break;
            case LOADER_ID_ALBUM:
                cursorLoader = new CursorLoader(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
                        MediaStore.Audio.Media.ALBUM);
                break;
            case LOADER_ID_TITLE:
                cursorLoader = new CursorLoader(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
                        MediaStore.Audio.Media.TITLE);
                break;
        }
        return cursorLoader;
    }

    /**
     * When the load has finished we create a new adapter of the cursor we
     * receive from the media store content provider. The adapter is then set to
     * the listvew. The adapter uses ARIST, ALBUM and TITLE to be displayed to the
     * user.
     */
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor)
    {
        if(adapter == null) 
        {
            adapter = new MediaSelectorAdapter(getApplicationContext(), R.layout.activity_media_selector, cursor, new String[] {
                MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE }, new int[] { R.id.text_1,
                R.id.text_2, R.id.text_3 }, Adapter.NO_SELECTION, selectedItems);
            listView.setAdapter(adapter);
        }
        else
        {
            adapter.swapCursor(cursor);
        }
    }

    /**
     * WHen the loader is reset we just pass in null as the cursor to the
     * adapter.
     */
    public void onLoaderReset(Loader<Cursor> cursorLoader)
    {
        adapter.swapCursor(null);
    }
}

Este es mi código de adaptador:

/**
 * This adapter is used by the media selector activity to display the list rows.
 * It is needed to keep track of which checkboxes have been checked and which
 * has not. The system is aggressive in trying to re-use views that are not
 * currently being displayed which leads to strange behaviour with the
 * checkboxes where they keep their "checked" state although they have not been
 * checked for a specific item.
 * 
 * The class is extending SimpleCursorAdapter for easy use of the cursor that
 * can be obtained from a database or content resolver.
 * 
 * @author Daniel Kvist
 * 
 */
public class MediaSelectorAdapter extends SimpleCursorAdapter
{
    private Context context;
    private ArrayList<Track> listItems;
    private ArrayList<Track> selectedItems;

    /**
     * The constructor takes the same parameters as an ordinary simple cursor
     * adapter and passes them up to the super class. It then loops through the
     * cursor and initiates an array which contains references to all the list
     * rows and if they have been checked or not.
     * 
     * @param context
     *            the context which to be displayed in
     * @param layout
     *            the layout file for the list view
     * @param cursor
     *            the cursor that points to the data
     * @param from
     *            the fields that are to be displayed
     * @param to
     *            the views to display the fields in
     * @param flags
     *            any special flags that can be used to determine the behaviour
     *            of the super class adapter
     * @param selectedItems2 
     */
    public MediaSelectorAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to, int flags, ArrayList<Track> selectedItems)
    {
        super(context, layout, cursor, from, to, flags);
        this.context = context;
        this.selectedItems = selectedItems;
        listItems = new ArrayList<Track>();

        while (cursor.moveToNext())
        {
            Track track = new Track(cursor.getString(0), cursor.getString(1), cursor.getString(2), cursor.getString(3),
                    cursor.getString(4), cursor.getString(5), cursor.getString(6));
            listItems.add(track);
        }
    }

    /**
     * Overridden method that getView uses to keep track of how many items the
     * adapter has.
     */
    @Override
    public int getCount()
    {
        return listItems.size();
    }

    /**
     * Called by the system to get a specific item.
     */
    @Override
    public Track getItem(int position)
    {
        return listItems.get(position);
    }

    /**
     * Called by the system to get the id/position for an item.
     */
    @Override
    public long getItemId(int position)
    {
        return position;
    }

    /**
     * Reuses old views if they have not already been reset and inflates new
     * views for the rows in the list that needs a new one. It the adds a
     * listener to each checkbox that is used to store information about which
     * checkboxes have been checked or not. Finally we set the checked status of
     * the checkbox and let the super class do it's thing.
     */
    @Override
    public View getView(final int position, View convertView, ViewGroup parent)
    {
        if (convertView == null)
        {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.media_selector_item_layout, null);
        }
        final CheckBox checkBox = (CheckBox) convertView.findViewById(R.id.checkbox);
        checkBox.setOnClickListener(new OnClickListener()
        {
            public void onClick(View v)
            {
                CheckBox cb = (CheckBox) v.findViewById(R.id.checkbox);
                if (cb.isChecked())
                {
                    selectedItems.add(listItems.get(position));
                }
                else if (!cb.isChecked())
                {
                    selectedItems.remove(listItems.get(position));
                }
            }
        });
        // If the selected items contains the current item, set the checkbox to be checked
        checkBox.setChecked(selectedItems.contains(listItems.get(position)));

        return super.getView(position, convertView, parent);
    }

    /**
     * Returns an array list with all the selected items as Track objects.
     * 
     * @return the selected items
     */
    public ArrayList<Track> getSelectedItems()
    {
        return selectedItems;
    }
}

Cualquier consejo, consejo u otra entrada es muy apreciada.

Gracias

Respuestas a la pregunta(1)

Su respuesta a la pregunta