Tôi chưa bao giờ hài lòng với mã trên CursorAdapter tùy chỉnh của mình cho đến hôm nay, tôi quyết định xem lại và khắc phục một vấn đề nhỏ khiến tôi bận tâm trong một thời gian dài (không có ai trong số các ứng dụng của tôi từng báo cáo vấn đề như vậy).Đây có phải là CursorAdapter tùy chỉnh cho một ListView được mã hóa đúng cho Android không?
Dưới đây là một mô tả nhỏ của câu hỏi của tôi:
CursorAdapter tùy chỉnh của tôi đè newView()
và bindView()
thay vì getView()
như hầu hết các ví dụ tôi thấy. Tôi sử dụng mẫu ViewHolder giữa 2 phương thức này. Nhưng vấn đề chính của tôi là với bố cục tùy chỉnh tôi đang sử dụng cho từng mục danh sách, nó chứa một số ToggleButton
.
Vấn đề là trạng thái nút không được giữ khi chế độ xem mục danh sách được cuộn ra khỏi chế độ xem và sau đó cuộn lại vào chế độ xem. Vấn đề này tồn tại vì cursor
chưa bao giờ biết rằng dữ liệu cơ sở dữ liệu đã thay đổi khi số ToggleButton
được nhấn và nó luôn kéo cùng một dữ liệu. Tôi đã cố gắng truy vấn lại con trỏ khi nhấp vào ToggleButton
và giải quyết vấn đề, nhưng nó rất chậm.
Tôi đã giải quyết vấn đề này và tôi đang đăng toàn bộ lớp học ở đây để được xem xét. Tôi đã nhận xét mã kỹ lưỡng cho câu hỏi cụ thể này để giải thích rõ hơn về các quyết định mã hóa của tôi.
Mã này có phù hợp với bạn không? Bạn sẽ cải thiện/tối ưu hóa hoặc thay đổi nó bằng cách nào đó?
P.S: Tôi biết CursorLoader là một cải tiến rõ ràng nhưng tôi không có thời gian để đối phó với các mã viết lớn như vậy trong thời gian này. Đó là điều tôi có trong lộ trình.
Dưới đây là các mã:
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);
}
}
Tôi thích ý tưởng về 'SparseArray', không biết về lớp đó. Nó trông giống như một cách hiệu quả hơn trong việc lưu bộ nhớ đệm nút thay vì lưu tất cả chúng trong một 'Danh sách'. Nhưng tôi không thích ý tưởng chỉ cam kết kết quả vào cơ sở dữ liệu khi người dùng rời khỏi hoạt động. Điều đó sẽ cần thêm mã để xử lý tình huống đó. Vì vậy, cuối cùng, tôi đoán tôi đang chọn bằng giải pháp thứ hai mà bạn liệt kê. Đó là cơ bản những gì tôi đã làm ở nơi đầu tiên. Tôi vẫn thích câu trả lời của bạn nhưng tôi sẽ có thể 1 hoặc 2 ngày nữa :) –