2012-02-24 12 views
11

Bugüne kadar özel CursorAdapter'imdeki koddan hiç memnun kalmamıştım ve uzun bir süre beni rahatsız eden küçük bir sorunu düzeltmeye karar verdim (ilginç bir şekilde, uygulamamın kullanıcılarının hiçbiri böyle bir sorun bildirmedi)).Android için düzgün şekilde kodlanmış bir ListView için bu özel CursorAdapter mi?

Benim özel CursorAdapter görüyorum çoğu örnekler olarak newView() ve bindView() yerine getView() geçersiz kılar:

İşte sorumu küçük açıklama bu. Bu 2 yöntem arasında ViewHolder desenini kullanıyorum. Ama benim ana sorun her liste öğesi için kullanıyorum özel düzeni ile oldu, bir ToggleButton içerir.

sorun

bir liste öğesi görünümü görünümü dışında kaydırılan ve ardından görünümüne geri scrollbed zaman düğme durumu muhafaza değil idi. Bu sorun, cursor, ToggleButton basıldığında veritabanı verilerinin değiştiğinden ve her zaman aynı verileri çekdiğinden hiçbir zaman haberdar olmadığından mevcuttu. ToggleButton'u tıklatırken imleci isteme girişiminde bulundum ve bu sorun çözüldü, ancak çok yavaştı.

Bu sorunu çözdüm ve inceleme için tüm sınıfı buraya gönderiyorum. Kodlama kararlarımı daha iyi açıklamak için bu özel soru için kodu iyice yorumladım.

Bu kod size uygun görünüyor mu? Bir şekilde geliştirir/optimize eder veya değiştirir misiniz?

P.S.: Ben CursorLoader bariz iyileşme olduğunu biliyorum ama böyle büyük kod şimdilik yeniden yazar başa vakit yok. Yine de yol haritasındaki bir şey.

İşte kod:

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

} 

cevap

4

Çözümünüz uygunudur bir I Belki, veritabanına aramalar için biraz optimizasyon getirmek için çalışacağım :) benim silah ekleyecektir. Çünkü görevin koşullarının Nitekim

, orada sadece üç çözüm şunlardır:

  1. Güncelleme sadece bir satır, yeniden sorgulamak imleç ve tüm öğeleri yeniden çizme. (Düz ileri, kaba kuvvet).
  2. Satırı güncelleyin, sonuçları önbelleğe alın ve çizim öğeleri için önbellek kullanın.
  3. Sonuçları önbelleğe alın, çizim öğeleri için önbelleği kullanın. Ve bu aktivite/parçadan ayrıldığınızda sonuçları veritabanına aktarın.

3. çözüm için değişiklikleri görmek için SparseArray'i kullanabilirsiniz.

Bir kez daha: başlangıçta bu dizi boş. Düğmeyi ilk kez değiştirdiğinizde (bir değişiklik var), diziye NoteData eklersiniz. Düğmeyi ikinci kez değiştirdiğinizde (geri alma var), NoteData'yı diziden kaldırırsınız. Ve bunun gibi.

İşlemi tamamladığınızda, yalnızca diziden istekte bulunun ve değişiklikleri veritabanına aktarın.

+0

Bir "SparseArray" fikrini beğendim, bu sınıfı bilmiyordum. Hepsini bir "Liste" içine kaydetmek yerine, düğme durumlarını önbelleğe almanın daha verimli bir yolu gibi görünüyor. Ancak, kullanıcı yalnızca etkinliği etkinleştirdiğinde sonuçları veritabanına kaydetmeyi sevmiyorum. Bu durumun üstesinden gelmek için fazladan kod gerek. Sonuç olarak, sanırım numaralandırdığınız ikinci çözümden vazgeçiyorum. Temelde ilk etapta ne yaptığım. Cevabını hala beğendim ama belki 1 ya da 2 gün daha gidiyorum :) –

1

Gördüğünüz şey, Android'in View kullanımıdır. İmleci tekrar sorgulayarak yanlış bir şey yaptığınızı düşünmüyorum. Sadece cursor.requery() işlevini kullanmayın.Bunun yerine önce her zaman önce toggle öğesini false olarak ayarlayın ve sonra imleci isteyin ve gerekiyorsa açın.

Belki bunu yapıyordunuz ve bir şeyi yanlış anlamışımdır, ancak bunu yaparken yavaş sonuç almanız gerektiğini düşünmüyorum.

Sözde kod:

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

Ben CursorLoader gitmeden önce beklerdi. Görünüşe göre CursorLoader türevleri CursorLoader ile çalışmaz.

İlgili konular