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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 00:35:23  来源:igfitidea点击:

notifydataSetChanged on Adapter will update with new items, but will not update the existing items

javaandroid

提问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 notifyDataSetChangedcan 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 itemis 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:

这里有一个类似的问题,有一个可能有效的解决方案:

ListView not refreshing already-visible items

ListView 不刷新已经可见的项目

回答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 ListViewdoesn'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 convertViewas 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 refreshHoldermethod is only invoked when the convertViewobject has a value in the heap.

refreshHolder仅当convertView对象在堆中具有值时才调用该方法。

That implies that when convertViewis 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

ListView 不刷新已经可见的项目

found by Kupsef

由 Kupsef 发现

回答by JAPS

or you can do it in this way:listadapter.clear(); listadapter.addAll(yourData);

或者你可以这样做:listadapter.clear(); listadapter.addAll(yourData);