Android ListView在调用notifyDataSetChanged后没有刷新

119

我的ListFragment代码

public class ItemFragment extends ListFragment {

    private DatabaseHandler dbHelper;
    private static final String TITLE = "Items";
    private static final String LOG_TAG = "debugger";
    private ItemAdapter adapter;
    private List<Item> items;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.item_fragment_list, container, false);        
        return view;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.setHasOptionsMenu(true);
        super.onCreate(savedInstanceState);
        getActivity().setTitle(TITLE);
        dbHelper = new DatabaseHandler(getActivity());
        items = dbHelper.getItems(); 
        adapter = new ItemAdapter(getActivity().getApplicationContext(), items);
        this.setListAdapter(adapter);

    }



    @Override
    public void onResume() {
        super.onResume();
        items.clear();
        items = dbHelper.getItems(); //reload the items from database
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        if(dbHelper != null) { //item is edited
            Item item = (Item) this.getListAdapter().getItem(position);
            Intent intent = new Intent(getActivity(), AddItemActivity.class);
            intent.putExtra(IntentConstants.ITEM, item);
            startActivity(intent);
        }
    }
}

我的ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

但是这并不会刷新ListView。即使重新启动应用程序,更新的项目也不会显示。我的ItemAdapter扩展了BaseAdapter

public class ItemAdapter extends BaseAdapter{

    private LayoutInflater inflater;
    private List<Item> items;
    private Context context;

    public ProjectListItemAdapter(Context context, List<Item> items) {
        super();
        inflater = LayoutInflater.from(context);
        this.context = context;
        this.items = items;

    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public Object getItem(int position) {
        return items.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemViewHolder holder = null;
        if(convertView == null) {
            holder = new ItemViewHolder();
            convertView = inflater.inflate(R.layout.list_item, parent,false);
            holder.itemName = (TextView) convertView.findViewById(R.id.topText);
            holder.itemLocation = (TextView) convertView.findViewById(R.id.bottomText);
            convertView.setTag(holder);
        } else {
            holder = (ItemViewHolder) convertView.getTag();
        }
        holder.itemName.setText("Name: " + items.get(position).getName());
        holder.itemLocation.setText("Location: " + items.get(position).getLocation());
        if(position % 2 == 0) {                                                                                 
            convertView.setBackgroundColor(context.getResources().getColor(R.color.evenRowColor));
        } else {    
            convertView.setBackgroundColor(context.getResources().getColor(R.color.oddRowColor));
        }
        return convertView;
    }

    private static class ItemViewHolder {
        TextView itemName;
        TextView itemLocation;
    }
}

有人能帮忙吗?


2
你测试过数据库操作是否正常工作了吗?适配器长什么样子?另外,如果你为“adapter”引用创建一个对象,为什么在下面一行要测试它是否为空? - user
代码没有抛出异常,我使用调试进行了检查。所有方法都没有出现错误。是的,那是一个愚蠢的错误。 - Coder
11个回答

235

查看您在ItemFragment中的onResume方法:

@Override
public void onResume() {
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); // reload the items from database
    adapter.notifyDataSetChanged();
}

在调用notifyDataSetChanged()之前你刚刚更新的不是适配器的私有字段private List<Item> items;,而是与碎片中声明相同的字段。适配器仍然存储着你在创建适配器时传递的项目列表的引用(例如,在碎片的onCreate中)。 使你的代码按照预期运行最简单(从修改次数的角度上看),但并不是优雅的方法就是简单地替换这一行:

    items = dbHelper.getItems(); // reload the items from database
    items.addAll(dbHelper.getItems()); // reload the items from database

更优雅的解决方案:

1)从ItemFragment中删除项目private List<Item> items; - 我们只需要在适配器中保留对它们的引用。

2)将onCreate更改为:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.setHasOptionsMenu(true);
    getActivity().setTitle(TITLE);
    dbHelper = new DatabaseHandler(getActivity());
    adapter = new ItemAdapter(getActivity(), dbHelper.getItems());
    setListAdapter(adapter);
}

3) 在ItemAdapter中添加方法:

public void swapItems(List<Item> items) {
    this.items = items;
    notifyDataSetChanged();
}

4)将您的onResume更改为:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}

把整个dbHelper的东西都移到Adapter里面不是更清晰吗?这样你只需要调用adapter.swapItems();,然后Adapter就会执行dbHelper.getItems()的操作。但无论如何,感谢你的回答 :) - Ansgar
7
为什么要清除并重新添加项目?这不正是notifyDataSetChanged()的目的吗? - Phil Ryan
1
@tomsaz,你能帮我解决这个问题吗?http://stackoverflow.com/questions/28148618/listview-not-refreshing-after-click-on-button - user4050065
1
谢谢@tomsaz Gawel,你的swapItems对我帮助很大,我不知道为什么我的adapter.notifydatasetchanged不起作用,因为我传递的“list”也已经更新了,即使我通过打印日志进行了检查。你能否解释一下这个概念? - Kimmi Dhingra
Tomasz,你能帮忙解决一个类似的问题吗:http://stackoverflow.com/questions/35850715/recyclerview-dont-show-inserted-sqlite-data-instantly/35850839#35850839我已经尝试了这个解决方案,但没有成功。 - user5366495
1
这个答案是正确的。问题在于 ADAPTER 的 Item ArrayList 没有被更新。这意味着你可以一直调用 notifydatasetchanged,但没有任何效果。适配器正在使用相同的数据集更新你的数据集,因此没有任何更改。 另一个可能更清晰的解决方案是: adapter.items = items; adapter.notifyDataSetChanged(); - Ray Li

25

您正在将重新加载的项目分配给 onResume() 中的全局变量 items,但这不会反映在 ItemAdapter 类中,因为它有自己的实例变量称为“items”。

为了刷新 ListView,请在 ItemAdapter 类中添加一个接受列表数据即 items 的 refresh() 方法。

class ItemAdapter
{
    .....

    public void refresh(List<Item> items)
    {
        this.items = items;
        notifyDataSetChanged();
    } 
}

使用以下代码更新 onResume()

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}

1
这是完全正确的。适配器的构造函数期望传递项目,但它只更新外部类的字段。 - LuxuryMode
嗨,Santhosh。你能看一下一个类似的问题吗:http://stackoverflow.com/questions/35850715/recyclerview-dont-show-inserted-sqlite-data-instantly/35850839#35850839 - user5366495

8
在onResume()中,更改此行。
items = dbHelper.getItems(); //reload the items from database

为了

items.addAll(dbHelper.getItems()); //reload the items from database

问题在于您从未通知您的适配器有关新项目列表的信息。如果您不想将新列表传递给适配器(似乎是这样),那么只需在调用 clear() 后使用 items.addAll 即可。这将确保您修改的是适配器具有引用的同一列表。


很令人困惑的是,adapter.clear()并不会强制适配器意识到视图需要刷新,但是adapter.add()adapter.addAll()却可以。不过还是感谢你的回答! - w3bshark
请注意,我使用的是 items.addAll() 而不是 adapter.addAll()。唯一让适配器对更改做出反应的是 notifyDataSetChanged。适配器看到更改的原因是 items 列表与适配器使用的列表相同。 - Justin Breitfeller

4
如果适配器已经设置,再次设置将不会刷新列表视图。相反,首先检查列表视图是否具有适配器,然后调用适当的方法。
我认为在设置列表视图时创建适配器的新实例并不是一个很好的想法。相反,创建一个对象。
BuildingAdapter adapter = new BuildingAdapter(context);

    if(getListView().getAdapter() == null){ //Adapter not set yet.
     setListAdapter(adapter);
    }
    else{ //Already has an adapter
    adapter.notifyDataSetChanged();
    }

此外,您可以尝试在UI线程上运行刷新列表操作:
activity.runOnUiThread(new Runnable() {         
        public void run() {
              //do your modifications here

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});

我不确定如何实现UI线程。我的主活动有3个片段(选项卡),问题中的代码与包含列表视图的一个片段相关。将项目传递给“ItemAdapter”的原因是我想要着色行,并且列表视图显示多个数据项。我已经发布了适配器的代码。 - Coder
你需要使用“this.”而不是“activity”将填充列表的代码放入我的示例代码中。 - AlexGo
在某些情况下,当您在不同的线程中运行notifyDataSetChanged()时,它可能不会更新。因此,上述解决方案对于某些情况是正确的。 - Ayman Al-Absi

4
如果您想在 onResume()onCreate() 或其他函数中更新您的列表视图,首先要认识到的是您不需要创建适配器的新实例,只需将数组再次填充即可。思路类似于:
private ArrayList<String> titles;
private MyListAdapter adapter;
private ListView myListView;

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    myListView = (ListView) findViewById(R.id.my_list);

    titles = new ArrayList<String>()

    for(int i =0; i<20;i++){
        titles.add("Title "+i);
    }

    adapter = new MyListAdapter(this, titles);
    myListView.setAdapter(adapter);
}


@Override
public void onResume(){
    super.onResume();
    // first clear the items and populate the new items
    titles.clear();
    for(int i =0; i<20;i++){
        titles.add("New Title "+i);
    }
    adapter.notifySetDataChanged();
}

所以根据这个答案,您应该在您的Fragment中使用相同的List<Item>。在第一次适配器初始化时,您可以使用项目填充列表并将适配器设置为您的列表视图。之后,在每次更改项目时,您必须从主要的List<Item> items中清除值,然后再次使用新项目填充它并调用notifySetDataChanged();
这就是它的工作原理:)。

谢谢回复。我按照您提到的更改进行了修改。我已经发布了我的代码。它仍然无法正常工作。现在,当添加新项目时,它甚至不显示列表视图。 - Coder
我已经修改了代码。奇怪的是,观察到项目在数据库中没有得到更新。 - Coder
这个线程是关于数据库的:http://stackoverflow.com/questions/14555332/android-sqlite-not-updating-the-data - Coder

3
adpter.notifyDataSetInvalidated();

在Activity类的onPause()方法中尝试这个。

3
一个来自AlexGo的答案解决了我的问题:
getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
         messages.add(m);
         adapter.notifyDataSetChanged();
         getListView().setSelection(messages.size()-1);
        }
});

在UI线程中,如果更新操作是由GUI事件触发的,那么List Update可以正常工作。

然而,当我从其他事件/线程更新列表时(例如从应用程序外部调用),更新将不会在UI线程中执行,并忽略了对getListView函数的调用。通过如上所述使用runOnUiThread调用更新操作解决了这个问题。谢谢!


3

试试这个

@Override
public void onResume() {
super.onResume();
items.clear();
items = dbHelper.getItems(); //reload the items from database
adapter = new ItemAdapter(getActivity(), items);//reload the items from database
adapter.notifyDataSetChanged();
}

2
如果您的列表包含在适配器中,调用更新列表的函数应该同时调用notifyDataSetChanged()
在UI线程中运行此功能对我很有帮助:
适配器内部的refresh()函数。
public void refresh(){
    //manipulate list
    notifyDataSetChanged();
}

然后从UI线程依次运行此函数。
getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});

1
这对我确实有所帮助,因为更新是通过不同的线程通过网络传输的。 - Chuck

1
尝试像这样:

试试这样:

this.notifyDataSetChanged();

代替:

adapter.notifyDataSetChanged();

你需要调用 notifyDataSetChanged() 方法来更新 ListView,而不是在适配器类中进行。

当然不会,唯一的机会是通过扩展ListView来实现。 - cmario

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