How to keep checkbox selection in cursor adapter when switching cursor?

后端 未结 1 1421
心在旅途
心在旅途 2021-01-29 10:43

I have a ListView that I\'m populating with information from the media store. I have checkboxes on each row to allow the user to select multiple rows. In the options menu, there

1条回答
  •  -上瘾入骨i
    2021-01-29 11:33

    Ok, so this is what I ended up with. Feel free to use it however you like. The solution is not to use a list at all but rather use the Cursor with getCursor() which is why we use a cursor adapter in the first place.

    Activity:

    /**
     * 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
    {
        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 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();
        }
    
        /**
         * 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 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 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 cursorLoader)
        {
            adapter.swapCursor(null);
        }
    }
    

    Adapter:

    /**
     * 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 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 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 getSelectedItems()
        {
            return selectedItems;
        }
    }
    

    0 讨论(0)
提交回复
热议问题