2012-10-02 18 views
-1

我有一個ListView,我用媒體商店的信息填充。我在每行上都有複選框,以允許用戶選擇多行。在選項菜單中,有一些菜單項觸發對媒體存儲的新查詢,並使用CursorLoader,當光標加載完成後,我在適配器中交換光標。切換光標時如何保持光標適配器中的複選框選擇?

在我的適配器中,我使用了一個ArrayList來跟蹤選中的項目以及跟蹤列表中所有項目的ArrayList。當光標更改以允許getView()方法檢查正確的複選框時,跟蹤所有項目的列表需要重新排序/重新構建。 我一直無法找到更新我的列表的方式,以便它與光標中項目的順序一致。

我試着重寫swapCursor,changeCursor和notifyDataSetChanged。在每次嘗試調用超級方法之前和之後,我會像在適配器的構造函數中完成列表一樣。

我看在這裏等等這些問題,這似乎有關,但我一直無法創建一個解決方案: 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

我已經找到了解決辦法,我會盡快發佈。它包括使用get cursor在getView()方法中創建Track對象,並用所有對象拋出列表。 – span

回答

1

好的,所以這就是我結束了。隨意使用它,只要你喜歡。解決方案不是使用列表,而是使用帶有getCursor()的Cursor,這就是爲什麼我們首先使用遊標適配器的原因。

活動:

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

如何在刷新視圖後刪除它? –