问题描述
我从来没有快乐与我自定义的CursorAdapter的code,直到今天,我决定进行审查,并确定这是困扰了我很长一段时间一个小问题(有趣的是,没有我的应用程序的用户不断的报告这样的问题)。
下面是我的问题,一个小说明:
我自定义的CursorAdapter覆盖 NewView的()
和 bindView()
而不是 getView( )
,因为我看到的大多数例子。我用这两个方法之间的ViewHolder模式。但我的主要问题是与我使用的每个列表项的自定义布局,它包含了一个切换按钮
。
问题是,各个按钮的状态是不是保持在一个列表项视图滚出视图,然后scrollbed回视图。这个问题存在,因为光标
从来都不知道,数据库中的数据改变时,切换按钮
是pressed它总是拉相同的数据。我试图重新查询光标点击切换按钮
并解决了这个问题的时候,但它是非常缓慢的。
我已经解决了这个问题,我在这里张贴全班同学进行审查。我已经评论了$ C $下彻底地为这个具体问题,以更好地解释我的编码决策。
这是否code好看呀?你会改进/优化或以某种方式改变它呢?
PS:我知道CursorLoader是一个明显的改善,但我没有时间去处理这么大的code重写暂且。这件事情我已经在路线图虽然。
这里的code:
公共类NotesListAdapter扩展的CursorAdapter实现OnClickListener {
私有静态类ViewHolder {
ImageView的图标;
TextView的称号;
TextView的描述;
切换按钮知名度;
}
私有静态类NoteData {
长ID;
INT iconId;
字符串称号;
字符串描述;
INT位置;
}
私人LayoutInflater mInflater;
私人NotificationHelper mNotificationHelper;
私人AgendaNotesAdapter mAgendaAdapter;
/ *
*该用于存储的切换按钮的状态为列表中的每个项
* /
私人列表<布尔> mToggleState;
私人诠释mColumnRowId;
私人诠释mColumnTitle;
私人诠释mColumnDescription;
私人诠释mColumnIconName;
私人诠释mColumnVisibility;
公共NotesListAdapter(上下文的背景下,光标光标,NotificationHelper帮手,AgendaNotesAdapter适配器){
超(背景下,光标);
mInflater = LayoutInflater.from(上下文);
/ *
* Helper类张贴通知状态栏和数据库适配器类更新
*当用户presses切换按钮中的任何项目的列表中的数据库中的数据
* /
mNotificationHelper =帮手;
mAgendaAdapter =适配器;
/ *
*有没有必要保持bindView(每次得到的列索引)(因为我见
*几个例子),所以我做一次,并保存在实例变量指标
* /
findColumnIndexes(光标);
/ *
*填充切换按钮状态的每个项目在列表中的相应值
*从数据库中的每个记录,但是这不就是一个缓慢的操作?
* /
为(mToggleState =新的ArrayList&其中;布尔>();!cursor.isAfterLast(); cursor.moveToNext()){
mToggleState.add(cursor.getInt(mColumnVisibility)!= 0);
}
}
@覆盖
公共查看NewView的(上下文的背景下,光标光标的ViewGroup父){
查看查看= mInflater.inflate(R.layout.list_item_note,NULL);
/ *
*本ViewHolder模式在这里只用prevent调用findViewById()所有的时间
*在bindView(),我们只需要找到各方面的意见,一旦
* /
ViewHolder viewHolder =新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 =(切换按钮)view.findViewById(R.id.togglebutton_visibility);
/ *
*我还用NewView的()来设置切换按钮点击侦听器列表中的每个项目
* /
viewHolder.visibility.setOnClickListener(本);
view.setTag(viewHolder);
返回查看;
}
@覆盖
公共无效bindView(查看视图,上下文的背景下,光标光标){
资源资源= context.getResources();
INT iconId = resources.getIdentifier(cursor.getString(mColumnIconName)
可拉伸,context.getPackageName());
字符串标题= cursor.getString(mColumnTitle);
字符串描述= cursor.getString(mColumnDescription);
/ *
*这是类似于ViewHolder图案和它的需要访问音符数据时
*的onClick()方法被解雇
* /
NoteData noteData =新NoteData();
/ *
*此数据是需要发布一个通知时的onClick()方法被解雇
* /
noteData.id = cursor.getLong(mColumnRowId);
noteData.iconId = iconId;
noteData.title =称号;
noteData.description =描述;
/ *
*此数据需要更新mToggleState [POS]时的onClick()方法被解雇
* /
noteData.position = cursor.getPosition();
/ *
*获取我们ViewHolder所有在NewView的发现视图的ID()
* /
ViewHolder viewHolder =(ViewHolder)view.getTag();
/ *
*需要的Html.fromHtml,但相关的code被剥夺
* /
viewHolder.icon.setImageResource(iconId);
viewHolder.title.setText(Html.fromHtml(标题));
viewHolder.description.setText(Html.fromHtml(介绍));
/ *
*从价值mToggleState设置切换按钮状态该列表项[POS]
而不是从与数据库中获取它*'cursor.getInt(mColumnVisibility)!= 0'
*否则国家将是不正确的,如果它是在项目视图之间滚动改变
*拿出来看和滚动回视图
* /
viewHolder.visibility.setChecked(mToggleState.get(noteData.position));
/ *
*此外,保存时的onClick()被炒鱿鱼要访问的音符数据
* /
viewHolder.visibility.setTag(noteData);
}
@覆盖
公共无效的onClick(视图查看){
/ *
*直接从切换按钮状态,获取新的状态
* /
布尔知名度=((切换按钮)查看).isChecked();
/ *
*获取后需要我们所有的音符数据(或删除)的通知
* /
NoteData noteData =(NoteData)view.getTag();
/ *
*在切换按钮的状态变化,更新mToggleState [POS],以反映新的变化
* /
mToggleState.set(noteData.position,可见性);
/ *
*发布通知,或从状态栏根据切换按钮的状态将其删除
* /
如果(知名度){
mNotificationHelper.postNotification(
noteData.id,noteData.iconId,noteData.title,noteData.description);
} 其他 {
mNotificationHelper.cancelNotification(noteData.id);
}
/ *
*更新与新的切换按钮的状态的数据库音符条,而不需要
*重新查询光标(这是缓慢的,我测试过它),以反映新的切换按钮的状态
*在列表中,因为该值被保存在mToggleState [POS]几行上方
* /
mAgendaAdapter.updateNote(noteData.id,NULL,NULL,NULL,NULL,可见性);
}
私人无效findColumnIndexes(光标指针){
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);
}
}
您的解决方案是最佳的,我将它添加到我的武器:)也许,我会尽量把一些优化的调用数据库。
实际上,该任务的条件,因为,只有三种解决方法:
- 更新只有一行,重新查询光标和重绘的所有项目。 (直进,蛮力)。
- 更新行,缓存结果,并使用高速缓存绘制的项目。
- 缓存的结果,使用缓存绘制的项目。而当你离开这个活动/片段然后将结果提交到数据库中。
有关第三解决方案,您可以使用SparseArray为寻找变化。
私人SparseArray< NoteData> mArrayViewHolders;
公共无效的onClick(视图查看){
//这里你有NoteData逻辑。
//启动我的提高
如果(mArrayViewHolders.get(selectedPosition)== NULL){
//使更改阵列
mArrayViewHolders.put(selectedPosition,noteData);
} 其他 {
//回滚的变化
mArrayViewHolders.delete(selectedPosition);
}
//我提高终端
//我们不更改提交到数据库中。
}
再次:从一开始这个数组为空。当您切换按钮第一次(有变化),添加NoteData阵列。当您切换按钮第二次(有一个回滚),你从数组中删除NoteData。等等。
当你完成,只要求该阵列并推动变成数据库。
I have never been happy with the code on my custom CursorAdapter until today I decided to review it and fix a little problem that was bothering me for a long time (interestingly enough, none of the users of my app ever reported such a problem).
Here's a small description of my question:
My custom CursorAdapter overrides newView()
and bindView()
instead of getView()
as most examples I see. I use the ViewHolder pattern between these 2 methods. But my main issue was with the custom layout I'm using for each list item, it contains a ToggleButton
.
The problem was that the button state was not kept when a list item view scrolled out of view and then scrollbed back into view. This problem existed because the cursor
was never aware that the database data changed when the ToggleButton
was pressed and it was always pulling the same data. I tried to requery the cursor when clicking the ToggleButton
and that solved the problem, but it was very slow.
I have solved this issue and I'm posting the whole class here for review. I've commented the code thoroughly for this specific question to better explain my coding decisions.
Does this code look good to you? Would you improve/optimize or change it somehow?
P.S: I know the CursorLoader is an obvious improvement but I don't have time to deal with such big code rewrites for the time being. It's something I have in the roadmap though.
Here's the code:
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);
}
}
Your solution is optimal an I will add it to my weapons :) Maybe, I'll try to bring a little optimization for the calls to database.
Indeed, because of conditions of the task, there are only three solutions:
- Update only one row, requery cursor and redraw all items. (Straight-forward, brute force).
- Update the row, cache the results and use cache for drawing items.
- Cache the results, use cache for drawing items. And when you leave this activity/fragment then commit the results to database.
For 3rd solution you can use SparseArray for looking for the changes.
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.
}
Once again: from the start this array is empty. When you toggle the button first time (there is a change), you add NoteData to array. When you toggle the button second time (there is a rollback), you remove NoteData from array. And so on.
When you're finishing, just request the array and push the changes into database.
这篇关于这是自定义的CursorAdapter的一个ListView妥善codeD为Android?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!