我从未对我的自定义CursorAdapter上的代码感到满意,直到今天我决定复习它并修复一个困扰我很长时间的小问题(有趣的是,我的应用程序的用户都没有报告过这样的问题问题)。
以下是我的问题的一个小描述:
我的自定义CursorAdapter会覆盖newView()
和bindView()
,而不是getView()
,我看到的大多数示例都是如此。我在这两种方法之间使用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);
}
}
答案 0 :(得分:4)
你的解决方案是最佳的,我会把它添加到我的武器:)也许,我会尝试为数据库的调用带来一些优化。
实际上,由于任务的条件,只有三种解决方案:
对于第三种解决方案,您可以使用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。等等。
完成后,只需请求数组并将更改推送到数据库中。
答案 1 :(得分:1)
您所看到的是View重复使用Android。通过再次查询光标我不认为你做错了什么。只是不要使用cursor.requery()函数。
相反,首先将切换设置为false,然后询问光标并在必要时将其打开。
也许你这样做了,我误解了一些东西,但是我不认为你应该做得很慢。
的伪代码:
getView(){
setToggleFalse();
boolean value = Boolean.valueOf(cursor.getString('my column'));
if (value){
setToggleTrue();
}
}
答案 2 :(得分:1)
我会在去CursorLoader之前等待。因为看起来CursorLoader派生不会使用CursorLoader。