2012-02-24 56 views
11

我從未對自定義的CursorAdapter上的代碼感到滿意,直到今天我決定檢查它並修復一個困擾我很長時間的小問題(有趣的是,我的應用程序的用戶都沒有報告過這樣的問題)。這是一個ListView正確編碼爲Android的自定義CursorAdapter嗎?

這裏是我的問題的一小描述:

我定製的CursorAdapter覆蓋newView()bindView()代替getView()因爲大多數的例子,我看到的。我在這兩種方法之間使用ViewHolder模式。但我的主要問題是我爲每個列表項目使用的自定義佈局,它包含一個ToggleButton

問題是當列表項視圖滾動出視圖然後滾動回視圖時,按鈕狀態並未保持。這個問題的存在是因爲cursor從未意識到數據庫數據在按下ToggleButton時發生了改變,並且它總是提取相同的數據。我試圖在點擊ToggleButton時查詢光標並解決了問題,但速度很慢。

我已經解決了這個問題,我在這裏發佈整個班級以供審查。爲了更好地解釋我的編碼決定,我已針對此特定問題徹底評論了代碼。

這段代碼對你來說看起來不錯嗎?你會改進/優化或改變它嗎? P:我知道CursorLoader是一個明顯的改進,但我暫時沒有時間處理這樣的大代碼重寫。這是我在路線圖中的一些東西。

下面的代碼:

public class NotesListAdapter extends CursorAdapter implements OnClickListener { 

    private static class ViewHolder { 
     ImageView icon; 
     TextView title; 
     TextView description; 
     ToggleButton visibility; 
    } 

    private static class NoteData { 
     long id; 
     int iconId; 
     String title; 
     String description; 
     int position; 
    } 

    private LayoutInflater mInflater; 

    private NotificationHelper mNotificationHelper; 
    private AgendaNotesAdapter mAgendaAdapter; 

    /* 
    * This is used to store the state of the toggle buttons for each item in the list 
    */ 
    private List<Boolean> mToggleState; 

    private int mColumnRowId; 
    private int mColumnTitle; 
    private int mColumnDescription; 
    private int mColumnIconName; 
    private int mColumnVisibility; 

    public NotesListAdapter(Context context, Cursor cursor, NotificationHelper helper, AgendaNotesAdapter adapter) { 
     super(context, cursor); 

     mInflater = LayoutInflater.from(context); 

     /* 
     * Helper class to post notifications to the status bar and database adapter class to update 
     * the database data when the user presses the toggle button in any of items in the list 
     */ 
     mNotificationHelper = helper; 
     mAgendaAdapter = adapter; 

     /* 
     * There's no need to keep getting the column indexes every time in bindView() (as I see in 
     * a few examples) so I do it once and save the indexes in instance variables 
     */ 
     findColumnIndexes(cursor); 

     /* 
     * Populate the toggle button states for each item in the list with the corresponding value 
     * from each record in the database, but isn't this a slow operation? 
     */ 
     for(mToggleState = new ArrayList<Boolean>(); !cursor.isAfterLast(); cursor.moveToNext()) { 
      mToggleState.add(cursor.getInt(mColumnVisibility) != 0); 
     } 
    } 

    @Override 
    public View newView(Context context, Cursor cursor, ViewGroup parent) { 
     View view = mInflater.inflate(R.layout.list_item_note, null); 

     /* 
     * The ViewHolder pattern is here only used to prevent calling findViewById() all the time 
     * in bindView(), we only need to find all the views once 
     */ 
     ViewHolder viewHolder = new ViewHolder(); 

     viewHolder.icon = (ImageView)view.findViewById(R.id.imageview_icon); 
     viewHolder.title = (TextView)view.findViewById(R.id.textview_title); 
     viewHolder.description = (TextView)view.findViewById(R.id.textview_description); 
     viewHolder.visibility = (ToggleButton)view.findViewById(R.id.togglebutton_visibility); 

     /* 
     * I also use newView() to set the toggle button click listener for each item in the list 
     */ 
     viewHolder.visibility.setOnClickListener(this); 

     view.setTag(viewHolder); 

     return view; 
    } 

    @Override 
    public void bindView(View view, Context context, Cursor cursor) { 
     Resources resources = context.getResources(); 

     int iconId = resources.getIdentifier(cursor.getString(mColumnIconName), 
       "drawable", context.getPackageName()); 

     String title = cursor.getString(mColumnTitle); 
     String description = cursor.getString(mColumnDescription); 

     /* 
     * This is similar to the ViewHolder pattern and it's need to access the note data when the 
     * onClick() method is fired 
     */ 
     NoteData noteData = new NoteData(); 

     /* 
     * This data is needed to post a notification when the onClick() method is fired 
     */ 
     noteData.id = cursor.getLong(mColumnRowId); 
     noteData.iconId = iconId; 
     noteData.title = title; 
     noteData.description = description; 

     /* 
     * This data is needed to update mToggleState[POS] when the onClick() method is fired 
     */ 
     noteData.position = cursor.getPosition(); 

     /* 
     * Get our ViewHolder with all the view IDs found in newView() 
     */ 
     ViewHolder viewHolder = (ViewHolder)view.getTag(); 

     /* 
     * The Html.fromHtml is needed but the code relevant to that was stripped 
     */ 
     viewHolder.icon.setImageResource(iconId); 
     viewHolder.title.setText(Html.fromHtml(title)); 
     viewHolder.description.setText(Html.fromHtml(description)); 

     /* 
     * Set the toggle button state for this list item from the value in mToggleState[POS] 
     * instead of getting it from the database with 'cursor.getInt(mColumnVisibility) != 0' 
     * otherwise the state will be incorrect if it was changed between the item view scrolling 
     * out of view and scrolling back into view 
     */ 
     viewHolder.visibility.setChecked(mToggleState.get(noteData.position)); 

     /* 
     * Again, save the note data to be accessed when onClick() gets fired 
     */ 
     viewHolder.visibility.setTag(noteData); 
    } 

    @Override 
    public void onClick(View view) { 
     /* 
     * Get the new state directly from the toggle button state 
     */ 
     boolean visibility = ((ToggleButton)view).isChecked(); 

     /* 
     * Get all our note data needed to post (or remove) a notification 
     */ 
     NoteData noteData = (NoteData)view.getTag(); 

     /* 
     * The toggle button state changed, update mToggleState[POS] to reflect that new change 
     */ 
     mToggleState.set(noteData.position, visibility); 

     /* 
     * Post the notification or remove it from the status bar depending on toggle button state 
     */ 
     if(visibility) { 
      mNotificationHelper.postNotification(
        noteData.id, noteData.iconId, noteData.title, noteData.description); 
     } else { 
      mNotificationHelper.cancelNotification(noteData.id); 
     } 

     /* 
     * Update the database note item with the new toggle button state, without the need to 
     * requery the cursor (which is slow, I've tested it) to reflect the new toggle button state 
     * in the list because the value was saved in mToggleState[POS] a few lines above 
     */ 
     mAgendaAdapter.updateNote(noteData.id, null, null, null, null, visibility); 
    } 

    private void findColumnIndexes(Cursor cursor) { 
     mColumnRowId = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ROW_ID); 
     mColumnTitle = cursor.getColumnIndex(AgendaNotesAdapter.KEY_TITLE); 
     mColumnDescription = cursor.getColumnIndex(AgendaNotesAdapter.KEY_DESCRIPTION); 
     mColumnIconName = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ICON_NAME); 
     mColumnVisibility = cursor.getColumnIndex(AgendaNotesAdapter.KEY_VISIBILITY); 
    } 

} 

回答

4

你的解決方案是最佳的,我將它添加到我的武器:)也許,我會嘗試帶來了些許優化的調用數據庫。

因爲任務的條件

事實上,只有三種解決方案:

  1. 更新只有一行,再次查詢光標和重繪的所有項目。 (直向前,蠻力)。
  2. 更新該行,緩存結果並使用緩存來繪製項目。
  3. 緩存結果,使用緩存來繪製項目。當你離開這個活動/片段時,將結果提交給數據庫。

對於第三種解決方案,您可以使用SparseArray查找更改。

private SparseArray<NoteData> mArrayViewHolders; 

public void onClick(View view) { 
    //here your logic with NoteData. 
    //start of my improve 
    if (mArrayViewHolders.get(selectedPosition) == null) { 
     // put the change into array 
     mArrayViewHolders.put(selectedPosition, noteData); 
    } else { 
     // rollback the change 
     mArrayViewHolders.delete(selectedPosition); 
    } 
    //end of my improve 
    //we don't commit the changes to database. 
} 

再一次從頭開始,這個數組是空的。當您第一次切換按鈕(有變化)時,您將NoteData添加到數組。當你第二次切換按鈕時(有回滾),你從數組中移除NoteData。等等。

完成後,請求數組並將更改推送到數據庫中。

+0

我喜歡'SparseArray'的想法,不知道那個類。它看起來像緩存按鈕狀態的一種更有效的方式,而不是將它們全部保存在「List」中。但我不喜歡這個想法,只在用戶離開活動時將結果提交給數據庫。這需要額外的代碼來處理這種情況。所以,最後,我想我正在選擇你列舉的第二個解決方案。這基本上是我在做的第一件事。我仍然喜歡你的答案,但我可能會再過1或2天:) – 2012-02-29 21:54:43

1

你所看到的是View再次使用Android。通過再次查詢光標,我認爲你沒有做錯什麼。只是不使用cursor.requery()函數。

相反,首先將切換開關置於false,然後詢問光標並在必要時將其打開。

也許你是這麼做的,我誤解了一些東西,但我認爲你不應該有這麼慢的結果。

僞代碼:

getView(){ 
setToggleFalse(); 
boolean value = Boolean.valueOf(cursor.getString('my column')); 
if (value){ 
    setToggleTrue(); 
} 
} 
1

我纔去CursorLoader等待。因爲它似乎CursorLoader衍生物不與世界的CursorLoader。

相關問題