2012-02-24 14 views
11

私は今まで私のカスタムCursorAdapterのコードに満足していませんでした。私はそれを見直し、私を長い間悩ましていた小さな問題を修正することにしました。(面白いことに、私のアプリのユーザーの誰も、 )。ListViewのこのカスタムCursorAdapterは、Android用に正しくコーディングされていますか?

ここに私の質問の小さな説明:私が見るほとんどの例として、代わりにgetView()newView()bindView()を上書きしますCursorAdapter

私のカスタム。これらの2つのメソッドの間でViewHolderパターンを使用します。しかし、私の主な問題は、私が各リスト項目に使用しているカスタムレイアウトで、ToggleButtonが含まれていました。

問題は、リストアイテムビューが表示外にスクロールされ、スクロールバックされてビューに戻ったときにボタン状態が保持されないことでした。この問題は、cursorが、ToggleButtonが押されたときにデータベースのデータが変更されたことを決して知らず、常に同じデータを取得していたために存在しました。 ToggleButtonをクリックしたときにカーソルを再クエリしようとしましたが、それが問題を解決しましたが、非常に遅かったです。

私はこの問題を解決しました。ここでは全体のクラスを投稿してレビューしています。私はこの特定の質問についてコーディングの決定をよりよく説明するためにコードに完全にコメントしました。

このコードはお使いのように見えますか?あなたは改善/最適化するか、何とか変更しますか?

P.S:私は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

あなたのソリューションが最適であるAN私は自分の武器に追加します:)たぶん、私は、データベースへの呼び出しのために少し最適化を持参しようとするでしょうが。

  1. 更新のみ一列、再クエリカーソルとすべての項目を再描画:

    は実際には、ため、タスクの条件で、そこに3つだけの溶液です。 (ストレートフォワード、ブルートフォース)。

  2. 行を更新し、結果をキャッシュし、図面アイテムにキャッシュを使用します。
  3. 結果をキャッシュし、アイテムを描画するためにキャッシュを使用します。そして、このアクティビティ/フラグメントを残して、結果をデータベースにコミットします。

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'にすべて保存するのではなく、より効果的な方法です。しかし、私は、ユーザがその活動を離れるときに結果をデータベースにコミットするという考えが嫌いです。その状況を処理するためには余分なコードが必要になります。だから、結局、あなたが列挙した2番目の解決策を選んでいると思います。基本的に私が最初にやっていたことです。私はまだあなたの答えが好きでしたが、私はおそらく1または2日以上行くでしょう:) –

1

あなたが見ているのは、Androidのビューの再利用です。カーソルを再度照会して何か間違っているとは思わない。 cursor.requery()関数を使用しないでください。

代わりに、最初は常にトグルをfalseに設定してから、カーソルを尋ねて、必要があればオンにします。

多分あなたはそれをしていましたが、私は何か誤解しましたが、遅い結果を出すべきではないと思います。

擬似コード:

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

私はCursorLoaderに行く前に待機していました。それはCursorLoaderの派生物はCursorLoaderで心配していないようです。

関連する問題