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