从 notifyDataSetChanged 上的 RecyclerView 中删除占位符视图?
Removing placeholder view from RecyclerView on notifyDataSetChanged?
我正在做一个项目,我有一个 RecyclerView 显示来自 SQLiteDatabase 的项目。所以我构建了一个使用游标而不是数据列表的自定义适配器,这样我就可以直接从数据库查询中更新它。此外,我有一个 "placeholder" 视图,如果游标为空(没有返回行),它会被放入 recyclerView,基本上表示 "nothing here, add something by clicking +"。
我的问题是,当我在该状态下添加一个项目(当 RecyclerView 最初为空时)并调用 notifyDataSetChanged 时,RecyclerView 尝试回收占位符视图而不是扩充正确的视图项目,我得到一个 NullPointerException,因为我正在尝试更新从未初始化的 UI 个元素。
这是我的适配器现在的样子:
public class TextItemListAdapter extends RecyclerView.Adapter<TextItemListAdapter.ViewHolder> {
private List<TextItem> dataList;
private Cursor cursor;
private Context context;
private boolean empty = false;
private TextItemDataLayer dataLayer;
/**
* Constructor
* @param c Context
* @param curs The cursor over the data (Must be a cursor over the TextItems table)
*/
public TextItemListAdapter(Context c, Cursor curs) {
context = c;
dataLayer = new TextItemDataLayer(c);
dataLayer.openDB();
if (curs != null) {
switchCursor(curs);
}
empty = isEmpty(cursor);
}
/**
* ViewHolders populate the RecyclerView. Each item in the list is contained in a VewHolder.
*/
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
View parent;
TextItem textItem;
TextView location;
TextView text;
TextView time;
/**
* Create the viewholder for the item
* @param itemView the view contained in the viewHolder
*/
public ViewHolder(View itemView) {
super(itemView);
this.parent = itemView;
if (!empty) {
itemView.setClickable(true);
itemView.setOnClickListener(this);
}
location = (TextView) itemView.findViewById(R.id.textItem_item_title);
text = (TextView) itemView.findViewById(R.id.textItem_item_text);
time = (TextView) itemView.findViewById(R.id.textItem_item_time);
}
/**
* Handle a click event on an TextItem
* @param v
*/
@Override
public void onClick(View v) {
Intent i = new Intent(context, EditTextItemActivity.class);
i.putExtra("textItem", textItem);
context.startActivity(i);
}
}
/**
* Creation of the viewholder
* @param parent
* @param i
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
View view;
if (empty) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_item_list_item_empty, parent, false);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_item_list_item, parent, false);
}
ViewHolder vh = new ViewHolder(view);
return vh;
}
/**
* When the viewholder is bound to the recyclerview, initialize things here
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (!empty) {
if (!cursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
holder.textItem = dataLayer.buildTextItem(cursor);
holder.location.setText(holder.textItem.getLocation().getName());
holder.text.setText(holder.textItem.getMessage());
holder.time.setText(holder.textItem.getTime());
}
}
/**
* Get the number of items in the dataset
*
* If the dataset is empty, return 1 so we can imflate the "empty list" viewHolder
* @return
*/
@Override
public int getItemCount() {
if (empty) {
return 1;
}
return cursor.getCount();
}
/**
* switch the existing cursor with a new one
* @param c
*/
public void switchCursor(Cursor c) {
// helper method in TextItemDataLayer that compares two cursors' contents
if (TextItemDataLayer.cursorsEqual(cursor, c)) {
c.close();
return;
}
if (cursor != null) {
cursor.close();
}
cursor = c;
cursor.moveToFirst();
empty = isEmpty(cursor);
notifyDataSetChanged();
}
/**
* Check to see if the cursor is empty
*
* @param c the cursor to check
* @return
*/
private boolean isEmpty(Cursor c) {
return (c == null || c.getCount() == 0);
}
}
我尝试过但还没有开始工作的一些事情:
notifyItemRemoved(0);
如果列表从空变为非空
更改 switchCursor 以比较两个游标并像这样调用 notifyDataSetChanged() 或 notifyItemRemoved(0)
// helper method in TextItemDataLayer that compares two cursors' contents
if (OterDataLayer.cursorsEqual(cursor, c)) {
c.close();
return;
}
if (!isEmpty(c) && isEmpty(cursor)) {
empty = false;
notifyItemRemoved(0);
// or notifyDataSetChanged();
}
if (cursor != null) {
cursor.close();
}
cursor = c;
cursor.moveToFirst();
empty = isEmpty(cursor);
notifyDataSetChanged();
我也考虑过让空的占位符视图不在 RecyclerView 中,而是在父视图中,以完全避免这个问题,尽管我认为就应用程序的整体结构而言,它更好在 RecyclerView 中处理。
您的问题只是理解 RecyclerView.Adapter
的工作原理。请查看 this 以了解与您的问题相关的说明。
My issue is that when I add an item in that state (when the RecyclerView is initially empty) and call notifyDataSetChanged, the RecyclerView attempts to recycle the placeholder view instead of inflating the correct view item
你是对的,它正在发生是因为你没有覆盖 getItemViewType (int position),并且 RecyclerView
无法知道实际上有两种不同的 ViewHolder
类型或状态。
我建议按照上面链接的答案,重构您的代码以提供两个不同的 ViewHolder
,然后实施 getItemViewType (int position)
。这些方面的内容:
@Override
public int getItemViewType(int position) {
if(empty)
return EMPTY_VIEW_TYPE_CODE;
return REGULAR_VIEW_TYPE_CODE;
}
我正在做一个项目,我有一个 RecyclerView 显示来自 SQLiteDatabase 的项目。所以我构建了一个使用游标而不是数据列表的自定义适配器,这样我就可以直接从数据库查询中更新它。此外,我有一个 "placeholder" 视图,如果游标为空(没有返回行),它会被放入 recyclerView,基本上表示 "nothing here, add something by clicking +"。
我的问题是,当我在该状态下添加一个项目(当 RecyclerView 最初为空时)并调用 notifyDataSetChanged 时,RecyclerView 尝试回收占位符视图而不是扩充正确的视图项目,我得到一个 NullPointerException,因为我正在尝试更新从未初始化的 UI 个元素。
这是我的适配器现在的样子:
public class TextItemListAdapter extends RecyclerView.Adapter<TextItemListAdapter.ViewHolder> {
private List<TextItem> dataList;
private Cursor cursor;
private Context context;
private boolean empty = false;
private TextItemDataLayer dataLayer;
/**
* Constructor
* @param c Context
* @param curs The cursor over the data (Must be a cursor over the TextItems table)
*/
public TextItemListAdapter(Context c, Cursor curs) {
context = c;
dataLayer = new TextItemDataLayer(c);
dataLayer.openDB();
if (curs != null) {
switchCursor(curs);
}
empty = isEmpty(cursor);
}
/**
* ViewHolders populate the RecyclerView. Each item in the list is contained in a VewHolder.
*/
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
View parent;
TextItem textItem;
TextView location;
TextView text;
TextView time;
/**
* Create the viewholder for the item
* @param itemView the view contained in the viewHolder
*/
public ViewHolder(View itemView) {
super(itemView);
this.parent = itemView;
if (!empty) {
itemView.setClickable(true);
itemView.setOnClickListener(this);
}
location = (TextView) itemView.findViewById(R.id.textItem_item_title);
text = (TextView) itemView.findViewById(R.id.textItem_item_text);
time = (TextView) itemView.findViewById(R.id.textItem_item_time);
}
/**
* Handle a click event on an TextItem
* @param v
*/
@Override
public void onClick(View v) {
Intent i = new Intent(context, EditTextItemActivity.class);
i.putExtra("textItem", textItem);
context.startActivity(i);
}
}
/**
* Creation of the viewholder
* @param parent
* @param i
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
View view;
if (empty) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_item_list_item_empty, parent, false);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_item_list_item, parent, false);
}
ViewHolder vh = new ViewHolder(view);
return vh;
}
/**
* When the viewholder is bound to the recyclerview, initialize things here
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (!empty) {
if (!cursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
holder.textItem = dataLayer.buildTextItem(cursor);
holder.location.setText(holder.textItem.getLocation().getName());
holder.text.setText(holder.textItem.getMessage());
holder.time.setText(holder.textItem.getTime());
}
}
/**
* Get the number of items in the dataset
*
* If the dataset is empty, return 1 so we can imflate the "empty list" viewHolder
* @return
*/
@Override
public int getItemCount() {
if (empty) {
return 1;
}
return cursor.getCount();
}
/**
* switch the existing cursor with a new one
* @param c
*/
public void switchCursor(Cursor c) {
// helper method in TextItemDataLayer that compares two cursors' contents
if (TextItemDataLayer.cursorsEqual(cursor, c)) {
c.close();
return;
}
if (cursor != null) {
cursor.close();
}
cursor = c;
cursor.moveToFirst();
empty = isEmpty(cursor);
notifyDataSetChanged();
}
/**
* Check to see if the cursor is empty
*
* @param c the cursor to check
* @return
*/
private boolean isEmpty(Cursor c) {
return (c == null || c.getCount() == 0);
}
}
我尝试过但还没有开始工作的一些事情:
notifyItemRemoved(0);
如果列表从空变为非空更改 switchCursor 以比较两个游标并像这样调用 notifyDataSetChanged() 或 notifyItemRemoved(0)
// helper method in TextItemDataLayer that compares two cursors' contents if (OterDataLayer.cursorsEqual(cursor, c)) { c.close(); return; } if (!isEmpty(c) && isEmpty(cursor)) { empty = false; notifyItemRemoved(0); // or notifyDataSetChanged(); } if (cursor != null) { cursor.close(); } cursor = c; cursor.moveToFirst(); empty = isEmpty(cursor); notifyDataSetChanged();
我也考虑过让空的占位符视图不在 RecyclerView 中,而是在父视图中,以完全避免这个问题,尽管我认为就应用程序的整体结构而言,它更好在 RecyclerView 中处理。
您的问题只是理解 RecyclerView.Adapter
的工作原理。请查看 this 以了解与您的问题相关的说明。
My issue is that when I add an item in that state (when the RecyclerView is initially empty) and call notifyDataSetChanged, the RecyclerView attempts to recycle the placeholder view instead of inflating the correct view item
你是对的,它正在发生是因为你没有覆盖 getItemViewType (int position),并且 RecyclerView
无法知道实际上有两种不同的 ViewHolder
类型或状态。
我建议按照上面链接的答案,重构您的代码以提供两个不同的 ViewHolder
,然后实施 getItemViewType (int position)
。这些方面的内容:
@Override
public int getItemViewType(int position) {
if(empty)
return EMPTY_VIEW_TYPE_CODE;
return REGULAR_VIEW_TYPE_CODE;
}