适配器上的notifydataSetChanged将使用新项目进行更新,但不会更新现有项目

时间:2014-05-14 22:16:42

标签: java android

我找不到与我的确切问题有关的具体内容,请继续阅读以了解这是什么。

我非常谨慎地确保在我的代码中的任何地方,我设置正确调用适配器上的notifyDataSetChanged,我初始化itemList一次,然后将其传递给适配器,并且不要重新启动初始化它。

它就像一个魅力,列表视图将自行更新,但仅适用于新项目。

对于现有项目,ListView将无法正确更新。

例如,如果我有一个列表视图显示一些自定义项目,我需要更新它,我这样做

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();
    }
}

现在listview将始终更新以显示新项目,但不会更新现有项目的视图。

这是真正的踢球者,它告诉我视图存在问题:如果我在更新的预先存在的项目上调用adapter.getItem(position);,则返回的项目将显示更新的更改,(意味着text1和text2将保持他们的新价值)即使它没有反映在列表视图中!

如果我打电话给listView.invalidateViews();,那么列表视图会显示更新,但我有两个问题,有时它会闪烁,有时,有时,如果我调用它并且它在{{1}之前运行}可以完成到列表视图,我得到一个&#34;列表视图没有通知数据变化&#34;错误!

有人对此有所了解吗?

notifyDataSetChanged

我想知道我应该做的是在使用复制构造函数添加到列表之前重新实例化我的所有项目。也许在通知适配器时,如果@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); } 仍然是相同的引用,适配器将假定没有更改,因此不会重绘该视图?或者也许适配器仅在收到通知时为新项目绘制新视图?

要添加其他详细信息,如果我向下滚动以使更新的视图离开屏幕,然后返回到它,它会显示正确的信息,因为listview会刷新/重新生成该视图。

我想我需要listview刷新所有当前视图,因此,item可能是我必须要做的。

有没有人对此有更多了解?

编辑: 根据要求,这是一个会出现此问题的适配器。

invalidateViews();

好的,我现在尝试了这个

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;
    }
}

和这个

    @Override
    public boolean hasStableIds()
    {
        return true;
    }

    @Override
    public long getItemId(int position)
    {
        return getItem(position).hashCode();
    }

其中我的hashcode是一个像这样使用的apache的反射构建器(应该工作导致基于值的哈希更改)

    @Override
    public boolean hasStableIds()
    {
        return false;
    }

    @Override
    public long getItemId(int position)
    {
        return getItem(position).hashCode();
    }

它并没有奏效。据我所知,stableIds什么也没做。

编辑:

在稳定的Ids的任何组合中,这些都不起作用。再一次,和往常一样,你必须在屏幕外滚动视图然后重新开启以便更新它。

    @Override
    public int hashCode()
    {
        return HashCodeBuilder.reflectionHashCode(this);
    }

8 个答案:

答案 0 :(得分:7)

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

ListView not refreshing already-visible items

答案 1 :(得分:3)

使用&#34;不稳定的ID&#34;如果调用notifyDatasetChanged (),一切都应该没问题,但似乎您的ListView并不知道某些现有项目必须更新。

也许您可以尝试实现稳定的ID并以某种方式滥用它们,即id更改项目更新。

@Override
public long getItemId(int position) {
    return getItem(position).getId();
}

@Override
public boolean hasStableIds() {
    return true;
}

另一个&#34;蛮力&#34;方法是构建一个新的Adapter并为ListView设置新的适配器。

答案 2 :(得分:2)

我分析了你所遇到的代码和问题,我得出了以下结论:

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

这意味着当convertView未分配给任何值时,不会发生更新。

解决方法是将itemHolder.refreshHolder(position)移出已插入的if-else条件块。

答案 3 :(得分:1)

adapter.notifyDataSetChanged()应该执行JOB,您需要确保在适配器外部操作的List itemsList是否与Adapter内部保持完全相同的实例。如果notifyDataSetChanged()不适合你,那肯定是这样的。

你的适配器也可能正在拿着一份副本&#39;您在构造函数中提供的列表,因此原始列表中的更改不会被反映...也许您可以向适配器引入一个方法,如:          adapter.setItems(列出项目)以确保项目确实已设置

您不需要在ListView上调用invalidateViews()...您需要做的就是确保适配器具有正确的列表以显示并触发 notifyDataSetChanged()

答案 4 :(得分:1)

而不是:

@Override
    public int getViewTypeCount()
    {
        return 2;
    }

尝试:

@Override
        public int getViewTypeCount()
        {
            return getCount();
        }

而不是放

ViewHolder viewHolder;

在getivew()方法中,尝试将它放在类构造函数

之前的类的开头

答案 5 :(得分:0)

为什么不直接更新视图

,而不是使用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在convertView上将不一致,当视图滚出视图并返回错误的ViewHolder时,将重复使用相同的视图。所以大多数时候视图都没有更新

答案 6 :(得分:0)

经过多次反复试验,这才有效。

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);
                }
            }
        }
    }
}

解决方案来自这里

ListView not refreshing already-visible items

由Kupsef发现

答案 7 :(得分:0)

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