自定义适配器中notifyDataSetChange无法工作

152
当我重新填充我的ListView时,我从我的Adapter中调用一个特定的方法。
问题:
当我从我的Adapter中调用updateReceiptsList时,数据被刷新了,但是我的ListView没有反映出这种变化。
问题:
为什么当我调用notifyDataSetChanged时,我的ListView不显示新数据?
Adapter:
public class ReceiptListAdapter extends BaseAdapter {

    public List<Receipt> receiptlist;
    private Context context;
    private LayoutInflater inflater;
    private DateHelpers dateH;

    public ReceiptListAdapter(Activity activity, Context mcontext, List<Receipt> rl) {
        context = mcontext;
        receiptlist = rl;
        Collections.reverse(receiptlist);
        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        dateH = new DateHelpers();
    }

    @Override
    public int getCount() {
        try {
            int size = receiptlist.size();
            return size;
        } catch(NullPointerException ex) {
            return 0;
        }
    }

    public void updateReceiptsList(List<Receipt> newlist) {
        receiptlist = newlist;
        this.notifyDataSetChanged();
    }

    @Override
    public Receipt getItem(int i) {
        return receiptlist.get(i);
    }

    @Override
    public long getItemId(int i) {
        return receiptlist.get(i).getReceiptId() ;
    }

    private String getPuntenString(Receipt r) {
        if(r.getPoints().equals("1")) {
            return "1 punt";
        }
        return r.getPoints()+" punten";
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View vi=convertView;

        final Receipt receipt = receiptlist.get(position);
        ReceiptViewHolder receiptviewholder;
        Typeface tf_hn = Typeface.createFromAsset(context.getAssets(), "helveticaneue.ttf");        
        Typeface tf_hn_bold = Typeface.createFromAsset(context.getAssets(), "helveticaneuebd.ttf");

        if (vi == null) { //convertview==null
            receiptviewholder = new ReceiptViewHolder();
            vi = inflater.inflate(R.layout.view_listitem_receipt, null);
            vi.setOnClickListener(null);
            vi.setOnLongClickListener(null);
            vi.setLongClickable(false);
            receiptviewholder.shop = (TextView) vi.findViewById(R.id.tv_listitemreceipt_shop);
            receiptviewholder.date = (TextView) vi.findViewById(R.id.tv_listitemreceipt_date);
            receiptviewholder.price = (TextView) vi.findViewById(R.id.tv_listitemreceipt_price);
            receiptviewholder.points = (TextView) vi.findViewById(R.id.tv_listitemreceipt_points);
            receiptviewholder.shop.setTypeface(tf_hn_bold);
            receiptviewholder.price.setTypeface(tf_hn_bold);
            vi.setTag(receiptviewholder);
        }else{//convertview is not null
            receiptviewholder = (ReceiptViewHolder)vi.getTag();
        }

        receiptviewholder.shop.setText(receipt.getShop());
        receiptviewholder.date.setText(dateH.timestampToDateString(Long.parseLong(receipt.getPurchaseDate())));
        receiptviewholder.price.setText("€ "+receipt.getPrice());
        receiptviewholder.points.setText(getPuntenString(receipt));

        vi.setClickable(false);
        return vi;
    }

    public static class ReceiptViewHolder {
        public TextView shop;
        public TextView date;
        public TextView price;
        public TextView points;
    }

    public Object getFilter() {
        // XXX Auto-generated method stub
        return null;
    }

}

--编辑:

找到解决方法

为了拥有一些可用的代码,我现在做了如下操作:

listview.setAdapter( new ReceiptListAdapter(activity,mcontext, -new dataset-);

能够运行,但与其预期的工作方式不同。


嗨,Jasper,请参考此链接...这将对你有所帮助。 - Tushar Pandey
尝试其他方法,如notifyItemInserted、notifyItemRemoved等。 - Reejesh PK
14个回答

373

改变你的方法,从

public void updateReceiptsList(List<Receipt> newlist) {
    receiptlist = newlist;
    this.notifyDataSetChanged();
}

public void updateReceiptsList(List<Receipt> newlist) {
    receiptlist.clear();
    receiptlist.addAll(newlist);
    this.notifyDataSetChanged();
}

所以你在适配器中保留与你的数据集相同的对象。


这是关于一个BaseAdapter的内容,该适配器不知道绑定到哪些数据...因此,如果我有一个自定义对象并使用该对象的自定义函数(例如custObject.getCount()custObject.getChildAt(int i)),并且我想要交换此对象,则notifyDataSetChanged无法正常工作...无论如何,我认为这个问题在ArrayAdapter中永远不会发生。 - prom85
3
请问您能否解释一下为什么第一种方法不起作用,而第二种方法可以使用BaseAdapter实现呢? - Tooroop
@tolgap 这个问题是针对你的 :) 第一个解决方案曾经对我有用过一段时间,但后来就停止工作了,我不得不像第二种方法那样改变代码。现在它运行正常。 - Tooroop
@Tooroop 我无法想象第一个解决方案会起作用,因为你所做的只是改变 receiptlist 变量的引用。 BaseAdapter 对此一无所知,因此 notifyDataSetChanged() 不会改变任何东西。您应该针对初始引用 List 进行操作,并从该 List 中删除/添加项。 - tolgap
2
请翻译以下与编程有关的内容,从英文到中文。仅返回翻译后的文本:不允许包含“感谢”或“+1”等评论,但在某些答案上我真的很想表达感谢 :) - Muhammad Saqib
显示剩余7条评论

27

我有同样的问题,然后我意识到。当我们创建适配器并将其设置为listview时,listview将指向适配器所持有的内存中的某个对象,该对象中的数据将显示在listview中。

adapter = new CustomAdapter(data);
listview.setadapter(adapter);

如果我们使用另一个数据创建适配器对象并调用notifydatasetchanged():

adapter = new CustomAdapter(anotherdata);
adapter.notifyDataSetChanged();

这不会影响listview中的数据,因为该列表指向不同的对象,该对象不知道适配器中的新对象,并且notifyDataSetChanged()没有任何影响。因此,我们应该更改对象中的数据,避免再次为适配器创建新对象。


16
您正在重新创建适配器对象,这并不高效。 - Alezis
3
我认为这正是Nhan所说的意思。 - Neeraj Sewani
大家好,我使用了你们提到的这些方法,但对我来说并没有起作用。请问有人可以检查一下我的问题吗?https://stackoverflow.com/questions/66827414/android-update-listview-through-baseadapter-in-postexecute-not-working - stefanosn

22

正如我在另一个回答线程(此处)中已经解释了这个问题的原因以及如何处理它。但我仍然在这里分享解决方案概述。

notifyDataSetChanged()不起作用的主要原因之一是:

适配器失去了对列表的引用。

创建并将新列表添加到适配器时,请始终遵循以下准则:

  1. 在全局声明时初始化 arrayList
  2. 直接将列表添加到适配器中,而无需检查 null 和空值。将适配器直接设置为列表(不要检查任何条件)。适配器保证您对 arrayList 的数据进行更改时会处理它,但永远不会丢失引用。
  3. 始终修改 arrayList 中的数据本身(如果您的数据完全新,则可以在实际添加数据到列表之前调用 adapter.clear()arrayList.clear()),但不要设置适配器,即如果新数据填充在 arrayList 中,则只需使用 adapter.notifyDataSetChanged()

希望这有所帮助。


1
谢谢!第二个提示很有帮助。 - Tiffany
大家好,我使用了你们提到的这些方法,但对我来说并没有起作用。请问有人可以帮忙检查一下我的问题吗?https://stackoverflow.com/questions/66827414/android-update-listview-through-baseadapter-in-postexecute-not-working - stefanosn

4

我在使用ListAdapter时遇到了同样的问题。

我让Android Studio为我实现方法,这是我得到的结果:

public class CustomAdapter implements ListAdapter {
    ...
    @Override
    public void registerDataSetObserver(DataSetObserver observer) {

    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {

    }
    ...
}

问题在于这些方法没有调用super的实现,因此notifyDataSetChange从未被调用。
要么手动删除这些覆盖,要么添加super调用,这样它就能正常工作了。
@Override
public void registerDataSetObserver(DataSetObserver observer) {
    super.registerDataSetObserver(observer);
}

@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
    super.unregisterDataSetObserver(observer);
}

大家好,我使用了你们提到的这些方法,但对我来说并没有起作用。请问有人可以检查一下我的问题吗?https://stackoverflow.com/questions/66827414/android-update-listview-through-baseadapter-in-postexecute-not-working - stefanosn

4
也许尝试刷新你的ListView:

receiptsListView.invalidate()

编辑:我想到了另一个想法。只是为了记录,尝试禁用列表视图缓存:

<ListView
    ...
    android:scrollingCache="false"
    android:cacheColorHint="@android:color/transparent"
    ... />

奇怪,我的最后一个想法是在数据更改后重新分配适配器到列表视图。但我想你也已经尝试过了。 - Rafal Gałka

2

谢谢。这个方法有效。但是我没有得到任何关于为什么notifyDataSetChanged()AutoCompleteTextView中不起作用的答案。 - undefined

1
如果您偶然着陆在这个主题上,并且想知道为什么您的情况下没有adapter.invaidate()adapter.clear()方法,那么可能是因为您正在使用RecyclerView.Adapter而不是此问题的提问者所使用的BaseAdapter。如果清除listarraylist不能解决您的问题,则可能是因为您正在创建adapter的两个或多个实例,例如:

MainActivity

...

adapter = new CustomAdapter(list);
adapter.notifyDataSetChanged();
recyclerView.setAdapter(adapter);

...

and
SomeFragment

...

adapter = new CustomAdapter(newList);
adapter.notifyDataSetChanged();

...

如果您期望在RecyclerView的充气视图列表中进行更改,那么在第二次创建适配器的新实例时,它不会发生,因为该实例未连接到RecyclerView。在第二个适配器中设置notifyDataSetChanged不会更改RecyclerView的内容。为此,在SomeFragment中创建RecyclerView的新实例,并将其附加到适配器的新实例中。
...

recyclerView = new RecyclerView();
adapter = new CustomAdapter();
recyclerView.setAdapter(adapter);

...

虽然我不建议制作多个相同的适配器和回收视图。

1
在我的情况下,我只是忘记在我的片段中添加mRecyclerView.setAdapter(adapter)

1
class StudentAdapter extends BaseAdapter {
    ArrayList<LichHocDTO> studentList;

    private void capNhatDuLieu(ArrayList<LichHocDTO> list){
        this.studentList.clear();
        this.studentList.addAll(list);
        this.notifyDataSetChanged();
    }
}

你可以尝试。对我有效。


0
我犯了一个非常新手的错误,就是在初始化适配器之前设置RecyclerView的适配器,像这样。
    // Assuume oneOffJobTasksListRVAdapter is declared already 
    recyclerView.setAdapter(oneOffJobTasksListRVAdapter);        
    oneOffJobTasksListRVAdapter = new OneOffJobTasksListRVAdapter();

交换这些行解决了我的问题。

    oneOffJobTasksListRVAdapter = new OneOffJobTasksListRVAdapter();
    recyclerView.setAdapter(oneOffJobTasksListRVAdapter);        

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接