Java 适配器上的 notifydataSetChanged 将更新新项目,但不会更新现有项目
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23666101/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
notifydataSetChanged on Adapter will update with new items, but will not update the existing items
提问by WIllJBD
I could not find something specifically relating to my exact issue, please read on to find out what that is.
我找不到与我的确切问题具体相关的内容,请继续阅读以了解那是什么。
I took great care to make sure that everywhere in my code, I am set up right to just call notifyDataSetChanged on the adapter, I initialize the itemList once, and pass that to the adapter, and don't re-initialize it ever.
我非常小心地确保在我的代码中的任何地方,我都设置为只在适配器上调用 notifyDataSetChanged,我初始化 itemList 一次,并将其传递给适配器,并且永远不要重新初始化它。
It works like a charm, and the list view will update itself, but only for new items.
它就像一个魅力,列表视图会自我更新,但仅限于新项目。
For existing items, the ListView will not update correctly.
对于现有项目,ListView 将不会正确更新。
For example if I have a listview that is displaying some custom items, and I need to update it I do this
例如,如果我有一个显示一些自定义项目的列表视图,并且我需要更新它,我会这样做
public void updateList(List<item> newItems)
{
if (adapter == null)
{
itemList.addAll(newItems);
adapter = new SomeAdapter(layoutInflator, itemList);
listView.setAdapter(adapter);
} else
{
// lets find all the duplicates and do all the updating
List<item> nonDuplicateItems = new ArrayList<item>();
for (Item newItem : newItems)
{
boolean isDuplicate = false;
for (Item oldItem : itemList)
{
// are these the same item?
if (newItem.id == oldItem.id)
{
isDuplicate = true;
// update the item
olditem.text1 = newItem.text1;
oldItem.text2 = newItem.text2;
}
}
if (isDuplicate == false)
{
// add the new item
nonDuplicateItems.add(newItem);
}
}
// I have tried just adding these new ones to itemList,
// but that doesnt seem to make the listview update the
// views for the old ones, so I thought thuis might help
// by clearing, merging, and then adding back
nonDuplicateItems.addAll(itemList);
itemList.clear();
itemList.addAll(nonDuplicateItems);
// finally notify the adapter/listview
adapter.notifyDataSetChanged();
}
}
now the listview will always update to show new items, but it will not update the views on the existing items.
现在列表视图将始终更新以显示新项目,但不会更新现有项目的视图。
Here is the real kicker that tells me it is an issue with the views: if I call adapter.getItem(position);
on a updated pre-existing item, the item returned will show the updated changes, (meaning text1 and text2 will hold their new values) even though it is not reflected in the listview!
这是真正的问题,告诉我这是视图的问题:如果我调用adapter.getItem(position);
更新的预先存在的项目,返回的项目将显示更新的更改,(意味着 text1 和 text2 将保留它们的新值)即使它没有反映在列表视图中!
If I call listView.invalidateViews();
then the list view will show the updates, but I have two problems with that, sometimes it flickers, and sometimes, just sometimes if I call it and it runs before the notifyDataSetChanged
can finish getting through to the listview, I get a "List view not notified of data change" error!
如果我调用listView.invalidateViews();
然后列表视图将显示更新,但我有两个问题,有时它会闪烁,有时,有时,只是有时如果我调用它并且它在notifyDataSetChanged
可以完成进入列表视图之前运行,我得到一个“列表视图未通知数据更改”错误!
Does anyone know anything about this?
有人对这个有了解吗?
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder viewHolder;
if (convertView == null)
{
convertView = layoutInflator.inflate(R.layout.item_comment, null);
// when the holder is created it will find the child views
// it will then call refreshHolder() on itself
viewHolder = new ViewHolder(convertView, position);
convertView.setTag(viewHolder);
} else
{
viewHolder = ((ViewHolder) convertView.getTag());
viewHolder.refreshHolder(position);
}
return convertView;
}
public void refreshHolder(int position)
{
this.position = position;
tvText1.setText(getItem(position).text1);
tvText2.setText(getItem(position).text2);
}
I wonder if what I should do is re-instantiate all my items before adding the to the list, using a copy constructor. Perhaps when notifying the adapter, the adapter will assume there is no changes if the item
is still the same reference, and so will not redraw that view? or perhaps the adapter only draws new views for new items when notified?
我想知道我是否应该做的是在添加到列表之前使用复制构造函数重新实例化我的所有项目。也许在通知适配器时,如果item
仍然是相同的引用,适配器将假定没有更改,因此不会重绘该视图?或者适配器仅在收到通知时才为新项目绘制新视图?
To add another detail, if I scroll down making the updated view go off screen, and then come back to it, it displays the correct info as the listview refreshes/remakes that view.
要添加另一个细节,如果我向下滚动使更新的视图离开屏幕,然后返回到它,它会在列表视图刷新/重新制作该视图时显示正确的信息。
I guess I am needing the listview to refresh all its current views so, invalidateViews();
may be what I have to do.
我想我需要列表视图来刷新其所有当前视图,所以这invalidateViews();
可能是我必须做的。
Does anyone know more about this?
有没有人了解更多?
EDIT: As requested here is an adapter that would have this issue.
编辑:根据这里的要求是一个会有这个问题的适配器。
public class ItemAdapter extends BaseAdapter
{
private final static int VIEWTYPE_PIC = 1;
private final static int VIEWTYPE_NOPIC = 0;
public List<Item> items;
LayoutInflater layoutInflator;
ActivityMain activity;
public ItemAdapter(List<Item> items, LayoutInflater layoutInflator, ActivityMain activity)
{
super();
this.items = new ArrayList<Item>();
updateItemList(items);
this.layoutInflator = layoutInflator;
this.activity = activity;
}
public void updateItemList(List<Item> updatedItems)
{
if (updatedItems != null && updatedItems.size() > 0)
{
// FIND ALL THE DUPLICATES AND UPDATE IF NESSICARY
List<Item> nonDuplicateItems = new ArrayList<Item>();
for (Item newItem : updatedItems)
{
boolean isDuplicate = false;
for (Item oldItem : items)
{
if (oldItem.getId().equals(newItem.getId()))
{
// IF IT IS A DUPLICATE, UPDATE THE EXISTING ONE
oldItem.update(newItem);
isDuplicate = true;
break;
}
}
// IF IT IS NOT A DUPLICATE, ADD IT TO THE NON-DUPLICATE LIST
if (isDuplicate == false)
{
nonDuplicateItems.add(newItem);
}
}
// MERGE
nonDuplicateItems.addAll(items);
// SORT
Collections.sort(nonDuplicateItems, new Item.ItemOrderComparator());
// CLEAR
this.items.clear();
// ADD BACK IN
this.items.addAll(nonDuplicateItems);
// REFRESH
notifyDataSetChanged();
}
}
public void removeItem(Item item)
{
items.remove(item);
notifyDataSetChanged();
}
@Override
public int getCount()
{
if (items == null)
return 0;
else
return items.size();
}
@Override
public Item getItem(int position)
{
if (items == null || position > getCount())
return null;
else
return items.get(position);
}
@Override
public long getItemId(int position)
{
return getItem(position).hashCode();
}
@Override
public int getItemViewType(int position)
{
Item item = getItem(position);
if (item.getPhotoURL() != null && URLUtil.isValidUrl(item.getPhotoURL()) == true)
{
return VIEWTYPE_PIC;
}
return VIEWTYPE_NOPIC;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ItemHolder itemHolder;
if (convertView == null)
{
if (getItemViewType(position) == VIEWTYPE_PIC)
{
convertView = layoutInflator.inflate(R.layout.item_pic, null);
} else
{
convertView = layoutInflator.inflate(R.layout.item, null);
}
// THIS CONSTRUCTOR ALSO CALLS REFRESH ON THE HOLDER FOR US
itemHolder = new ItemHolder(convertView, position);
convertView.setTag(itemHolder);
} else
{
itemHolder = ((ItemHolder) convertView.getTag());
itemHolder.refreshHolder(position);
}
return convertView;
}
@Override
public int getViewTypeCount()
{
return 2;
}
@Override
public boolean hasStableIds()
{
return false;
}
@Override
public boolean isEmpty()
{
return (getCount() < 1);
}
@Override
public boolean areAllItemsEnabled()
{
return true;
}
@Override
public boolean isEnabled(int position)
{
return true;
}
}
Ok I have now tried this
好的,我现在已经尝试过了
@Override
public boolean hasStableIds()
{
return true;
}
@Override
public long getItemId(int position)
{
return getItem(position).hashCode();
}
and this
和这个
@Override
public boolean hasStableIds()
{
return false;
}
@Override
public long getItemId(int position)
{
return getItem(position).hashCode();
}
where my hashcode is a reflection builder from apache used like so (Should work cause the hash changes based on values)
我的哈希码是一个来自 apache 的反射构建器,像这样使用(应该工作导致基于值的哈希更改)
@Override
public int hashCode()
{
return HashCodeBuilder.reflectionHashCode(this);
}
and it didn't work. From what I can tell stableIds is doing nothing.
它没有用。据我所知, stableIds 什么都不做。
EDIT:
编辑:
none of these work either, in any combination of stable Ids. Once again, and the same as always, you have to scroll the view offscreen and then back on in order for it to be updated.
在稳定 Id 的任何组合中,这些都不起作用。再一次,和往常一样,您必须将视图滚动到屏幕外,然后再重新打开以更新它。
listview.refreshDrawableState();
listview.requestLayout();
listview.invalidateViews();
采纳答案by kupsef
There is a similar issue here with a solution that may work:
这里有一个类似的问题,有一个可能有效的解决方案:
回答by Alécio Carvalho
the adapter.notifyDataSetChanged()should do the JOB, what you need to make sure is if the List itemsList you are manipulating outside the Adapter is the exact same instance that the Adapter is holding internally. If the notifyDataSetChanged() isn't working for you, that is definitely the case.
在adapter.notifyDataSetChanged()应该做的工作,你需要确保的是,如果你正在操纵适配器外的列表itemsList是完全相同的情况下,该适配器内部保存。如果notifyDataSetChanged() 对您不起作用,那绝对是这种情况。
Your adapter might also be holding a 'copy' of the list you provided in the constructor, so your changes on the original list won't be reflected...maybe you can introduce a method to the adapter like: adapter.setItems(List items)to ensure the items are really set
您的适配器可能还持有您在构造函数中提供的列表的“副本”,因此您对原始列表的更改不会被反映...也许您可以向适配器引入一个方法,例如: adapter.setItems(List项目)以确保项目真的设置
You don't need to call invalidateViews() on a ListView...all you need to do is make sure the Adapter has the correct list to display and trigger notifyDataSetChanged().
您不需要在 ListView 上调用 invalidateViews() ……您需要做的就是确保适配器具有正确的列表来显示和触发notifyDataSetChanged()。
回答by AlexS
With "unstable IDs" everything should be fine if calling notifyDatasetChanged ()
, but it seems your ListView
doesn't know that some existing items have to be updated.
使用“不稳定的 ID”,如果调用notifyDatasetChanged ()
,一切都应该没问题,但您ListView
似乎不知道某些现有项目必须更新。
Perhaps you can try to implement stable ids and misuse them in a way, that the id changes on item updates.
也许您可以尝试实现稳定的 id 并以某种方式滥用它们,即 id 在项目更新时发生变化。
@Override
public long getItemId(int position) {
return getItem(position).getId();
}
@Override
public boolean hasStableIds() {
return true;
}
Another "brute force" approach would be to build a new Adapter and set the new adapter for the ListView.
另一种“蛮力”方法是构建一个新的适配器并为 ListView 设置新的适配器。
回答by BlackBeard
Instead of using convertView.set/getTag()
, why not directly update the views
而不是使用convertView.set/getTag()
,为什么不直接更新视图
refreshHolder(convertView, position);
void refreshHolder(View v, int position)
{
((TextView)v.findViewById(R.id.Text1)).setText(getItem(position).text1);
((TextView)v.findViewById(R.id.Text2)).setText(getItem(position).text2);
}
setTag/getTag will not be consistent on convertView
as you are reusing the views, and same view will be reused when a view scrolled out of view and will return wrong ViewHolder
. So most of the times the view is not updated
setTag/getTag 在convertView
您重用视图时将不一致,并且当视图滚出视图时将重用相同的视图并返回错误ViewHolder
。所以大多数时候视图没有更新
回答by Jigar
instead of :
代替 :
@Override
public int getViewTypeCount()
{
return 2;
}
Try :
尝试 :
@Override
public int getViewTypeCount()
{
return getCount();
}
and instead of putting
而不是把
ViewHolder viewHolder;
in a getivew() method, try to put it in a starting of class before class constructor
在 getivew() 方法中,尝试将其放在类构造函数之前的类的开头
回答by GingerHead
I analyzed the code and the problem you are into and I came to the following conclusion:
我分析了代码和您遇到的问题,得出以下结论:
The refreshHolder
method is only invoked when the convertView
object has a value in the heap.
refreshHolder
仅当convertView
对象在堆中具有值时才调用该方法。
That implies that when convertView
is not assigned to any value, no update will happen.
这意味着当convertView
未分配给任何值时,不会发生更新。
A solution for this is to move itemHolder.refreshHolder(position)
out of the if-else condition block that you have inserted into.
对此的解决方案是移出itemHolder.refreshHolder(position)
您插入的 if-else 条件块。
回答by WIllJBD
After much trial and error, this is what worked.
经过多次反复试验,这就是有效的方法。
public class AutoRefreshListView extends ListView
{
public AutoRefreshListView(Context context)
{
super(context);
}
public AutoRefreshListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public AutoRefreshListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
private DataSetObserver mDataSetObserver = new AdapterDataSetObserver();
private ListAdapter mAdapter;
class AdapterDataSetObserver extends DataSetObserver
{
@Override
public void onChanged()
{
super.onChanged();
Log.d("AutoRefreshListView", "onChanged");
refreshVisibleViews();
}
@Override
public void onInvalidated()
{
super.onInvalidated();
Log.d("AutoRefreshListView", "onInvalidated");
refreshVisibleViews();
}
}
@Override
public void setAdapter(ListAdapter adapter)
{
super.setAdapter(adapter);
if (mAdapter != null)
{
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
mAdapter = adapter;
mAdapter.registerDataSetObserver(mDataSetObserver);
}
public void refreshVisibleViews()
{
Log.d("AutoRefreshListView", "refresh");
if (mAdapter != null)
{
for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition(); i++)
{
final int dataPosition = i - getHeaderViewsCount();
final int childPosition = i - getFirstVisiblePosition();
if (dataPosition >= 0 && dataPosition < mAdapter.getCount() && getChildAt(childPosition) != null)
{
Log.d("AutoRefreshListView", "onInvalidated -> Refreshing view (data=" + dataPosition + ",child=" + childPosition + ")");
mAdapter.getView(dataPosition, getChildAt(childPosition), AutoRefreshListView.this);
}
}
}
}
}
the solution is from here
解决方案来自这里
ListView not refreshing already-visible items
found by Kupsef
由 Kupsef 发现
回答by JAPS
or you can do it in this way:listadapter.clear(); listadapter.addAll(yourData);
或者你可以这样做:listadapter.clear(); listadapter.addAll(yourData);