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