切换光标时如何在光标适配器中保留复选框选择?

时间:2012-10-02 21:06:56

标签: android android-listview android-cursoradapter

我有一个ListView,我正在填充来自媒体商店的信息。我在每一行都有复选框,允许用户选择多行。在选项菜单中,有一些菜单项触发对媒体存储的新查询,并使用CursorLoader我在光标加载时交换适配器中的光标。

在我的适配器中,我使用的是一个跟踪已检查项目的ArrayList,以及一个跟踪列表中所有项目的项目。跟踪所有项目的列表需要在光标更改时重新排序/重建,以允许getView()方法检查正确的复选框。 我一直无法找到更新列表的方法,因此它与光标中项目的顺序一致。

我尝试重写swapCursor,changeCursor和notifyDataSetChanged。在每次尝试调用super方法之前和之后,我都按照适配器的构造函数来完成列表。

我在SO看到这些问题似乎有关但我无法创建解决方案: Problems with Listview adapter

Custom CursorAdapter and CheckBox states

Cursor not binding text correctly with custom adapter

这是我的活动代码:

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

这是我的适配器代码:

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

非常感谢任何提示,提示或其他输入。

由于

1 个答案:

答案 0 :(得分:1)

好的,这就是我最终的结果。随意使用它,但你喜欢。解决方案根本不是使用列表,而是使用Cursor和getCursor(),这就是我们首先使用游标适配器的原因。

的活动:

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

适配器:

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

    /**
     * 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)
    {
        Cursor c = getCursor();
        c.moveToPosition(position);
        final Track track = new Track(c.getString(0), c.getString(1), c.getString(2), c.getString(3),
                c.getString(4), c.getString(5), c.getString(6));

        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(track);
                }
                else if (!cb.isChecked())
                {
                    selectedItems.remove(track);
                }
            }
        });
        // If the selected items contains the current item, set the checkbox to be checked
        checkBox.setChecked(selectedItems.contains(track));
        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;
    }
}