我有一个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;
}
}
非常感谢任何提示,提示或其他输入。
由于
答案 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;
}
}