451

ListView添加/删除动态数据后如何刷新Android ?

4

25 回答 25

547

修改该适配器中的数据后调用notifyDataSetChanged()您的对象。Adapter

notifyDataSetChanged()可以在此 Google I/O 视频中查看有关如何/何时调用的一些其他细节。

于 2010-02-12T09:45:25.173 回答
212

你也可以使用这个:

myListView.invalidateViews();
于 2011-11-29T11:07:50.017 回答
157

请忽略此问题的所有invalidate(), invalidateViews(), requestLayout(), ... 答案。

正确的做法(幸运的是也标记为正确答案)是调用notifyDataSetChanged()您的 Adapter

故障排除

如果调用notifyDataSetChanged()不起作用,所有布局方法也无济于事。相信我ListView已正确更新。如果找不到差异,则需要检查适配器中的数据来自何处。

如果这只是您保留在内存中的集合,请在调用notifyDataSetChanged().

如果您正在使用数据库或服务后端,则必须在调用notifyDataSetChanged().

问题是这notifyDataSetChanged仅在数据集发生更改时才有效。因此,如果您没有发现变化,那就是寻找的地方。如果需要调试。

ArrayAdapter 与 BaseAdapter

我确实发现使用可让您管理集合的适配器(如 BaseAdapter)效果更好。像 ArrayAdapter 这样的适配器已经管理了自己的集合,因此更难找到正确的集合进行更新。在大多数情况下,这实际上只是不必要的额外难度。

用户界面线程

确实,这必须从 UI 线程中调用。其他答案有关于如何实现这一目标的示例。但是,仅当您从 UI 线程外部处理此信息时才需要这样做。那是来自服务或非 UI 线程。在简单的情况下,您将通过单击按钮或其他活动/片段来更新数据。所以仍然在 UI 线程中。无需总是弹出 runOnUiTrhead 。

快速示例项目

可以在https://github.com/hanscapelle/so-2250770.git找到。只需克隆并在 Android Studio (gradle) 中打开项目。这个项目有一个 MainAcitivity 构建一个ListView包含所有随机数据的。可以使用操作菜单刷新此列表。

我为此示例 ModelObject 创建的适配器实现公开了数据集合

public class MyListAdapter extends BaseAdapter {

    /**
     * this is our own collection of data, can be anything we 
     * want it to be as long as we get the abstract methods 
     * implemented using this data and work on this data 
     * (see getter) you should be fine
     */
    private List<ModelObject> mData;

    /**
     * our ctor for this adapter, we'll accept all the things 
     * we need here
     *
     * @param mData
     */
    public MyListAdapter(final Context context, final List<ModelObject> mData) {
        this.mData = mData;
        this.mContext = context;
    }

    public List<ModelObject> getData() {
        return mData;
    }

    // implement all abstract methods here
}

MainActivity 中的代码

public class MainActivity extends Activity {

    private MyListAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView list = (ListView) findViewById(R.id.list);

        // create some dummy data here
        List<ModelObject> objects = getRandomData();
        // and put it into an adapter for the list
        mAdapter = new MyListAdapter(this, objects);
        list.setAdapter(mAdapter);

        // mAdapter is available in the helper methods below and the 
        // data will be updated based on action menu interactions

        // you could also keep the reference to the android ListView 
        // object instead and use the {@link ListView#getAdapter()} 
        // method instead. However you would have to cast that adapter 
        // to your own instance every time
    }

    /**
     * helper to show what happens when all data is new
     */
    private void reloadAllData(){
        // get new modified random data
        List<ModelObject> objects = getRandomData();
        // update data in our adapter
        mAdapter.getData().clear();
        mAdapter.getData().addAll(objects);
        // fire the event
        mAdapter.notifyDataSetChanged();
    }

    /**
     * helper to show how only changing properties of data 
     * elements also works
     */
    private void scrambleChecked(){
        Random random = new Random();
        // update data in our adapter, iterate all objects and 
        // resetting the checked option
        for( ModelObject mo : mAdapter.getData()) {
            mo.setChecked(random.nextBoolean());
        }
        // fire the event
        mAdapter.notifyDataSetChanged();
    }
}

更多信息

另一个关于 listViews 强大功能的好帖子可以在这里找到:http ://www.vogella.com/articles/AndroidListView/article.html

于 2013-05-23T07:43:17.180 回答
42

随时调用 runnable :

runOnUiThread(run);

OnCreate(),你设置你的可运行线程:

run = new Runnable() {
    public void run() {
        //reload content
        arraylist.clear();
        arraylist.addAll(db.readAll());
        adapter.notifyDataSetChanged();
        listview.invalidateViews();
        listview.refreshDrawableState();
    }
};
于 2014-02-18T18:49:43.583 回答
11

我在动态刷新列表视图时遇到了一些问题。

在适配器上调用 notifyDataSetChanged()。

可以在此 Google I/O 视频中查看有关如何/何时调用 notifyDataSetChanged() 的一些其他细节。

notifyDataSetChanged() 在我的情况下无法正常工作[我从另一个类调用 notifyDataSetChanged]。就在我在正在运行的活动(线程)中编辑 ListView 的情况下。感谢克里斯托弗的视频给出了最后的提示。

在我的第二堂课中,我使用

Runnable run = new Runnable(){
     public void run(){
         contactsActivity.update();
     }
};
contactsActivity.runOnUiThread(run);

从我的活动中访问更新()。本次更新包括

myAdapter.notifyDataSetChanged();

告诉适配器刷新视图。据我所知,工作得很好。

于 2011-09-13T11:28:52.497 回答
9

如果您使用SimpleCursorAdapter ,请尝试在 Cursor 对象上调用requery() 。

于 2011-06-25T08:46:41.080 回答
5

如果您对 ListView Refreshment 仍然不满意,可以查看此代码段,这是用于从 DB 加载 listView,实际上您要做的只是在执行任何 CRUD 操作后重新加载 ListView 这不是最好的方法代码,但它会根据需要刷新 ListView..

它对我有用....如果您找到更好的解决方案,请分享...

…………
……
做你的 CRUD 操作..
……
......
DBAdapter.open();
DBAdapter.insert_into_SingleList();
// 带上 DB_results 并将其作为内容添加到列表中......
                                            ls2.setAdapter(新的 ArrayAdapter(DynTABSample.this,
    android.R.layout.simple_list_item_1, DBAdapter.DB_ListView));
                                            DBAdapter.close();
于 2010-08-25T11:34:15.753 回答
2

这篇文章中人们提出的解决方案是否有效主要取决于您设备的 Android 版本。例如,要使用 AddAll 方法,您必须将 android:minSdkVersion="10" 放入您的 android 设备中。

为了解决所有设备的这个问题,我在适配器中创建了自己的方法,并在继承自 ArrayAdapter 的 add 和 remove 方法中使用,该方法可以毫无问题地更新您的数据。

我的代码:使用我自己的数据类 RaceResult,您使用自己的数据模型。

结果GpRowAdapter.java

public class ResultGpRowAdapter extends ArrayAdapter<RaceResult> {

    Context context;
    int resource;
    List<RaceResult> data=null;

        public ResultGpRowAdapter(Context context, int resource, List<RaceResult> objects)           {
        super(context, resource, objects);

        this.context = context;
        this.resource = resource;
        this.data = objects;
    }

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

        ........
        }

        //my own method to populate data           
        public void myAddAll(List<RaceResult> items) {

        for (RaceResult item:items){
            super.add(item);
        }
    }

结果Gp.java

public class ResultsGp extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...........
    ...........
    ListView list = (ListView)findViewById(R.id.resultsGpList); 

    ResultGpRowAdapter adapter = new ResultGpRowAdapter(this,  R.layout.activity_result_gp_row, new ArrayList<RaceResult>()); //Empty data

   list.setAdapter(adapter);

   .... 
   ....
   ....
   //LOAD a ArrayList<RaceResult> with data

   ArrayList<RaceResult> data = new ArrayList<RaceResult>();
   data.add(new RaceResult(....));
   data.add(new RaceResult(....));
   .......

   adapter.myAddAll(data); //Your list will be udpdated!!!
于 2014-06-19T16:53:34.137 回答
1

如果你想在刷新时保持滚动位置,你可以这样做:

if (mEventListView.getAdapter() == null) {
    EventLogAdapter eventLogAdapter = new EventLogAdapter(mContext, events);
    mEventListView.setAdapter(eventLogAdapter);
} else {
    ((EventLogAdapter)mEventListView.getAdapter()).refill(events);
}

public void refill(List<EventLog> events) {
    mEvents.clear();
    mEvents.addAll(events);
    notifyDataSetChanged();
}

有关详细信息,请参阅Android ListView:刷新时保持滚动位置

于 2014-08-01T04:57:54.620 回答
1

对我来说,在 sql 数据库中更改信息后,没有什么可以刷新列表视图(具体可扩展列表视图),因此如果 notifyDataSetChanged() 没有帮助,您可以尝试先清除列表并在调用 notifyDataSetChanged() 后再次添加。例如

private List<List<SomeNewArray>> arrayList;
List<SomeNewArray> array1= getArrayList(...);
List<SomeNewArray> array2= getArrayList(...);
arrayList.clear();
arrayList.add(array1);
arrayList.add(array2);
notifyDataSetChanged();

希望它对你有意义。

于 2013-07-09T12:37:12.670 回答
1

您需要使用该列表中的单个对象,该对象是您正在膨胀的数据ListView。如果引用发生更改,则 notifyDataSetChanged()不起作用。每当您从列表视图中删除元素时,也将它们从您正在使用的列表中删除,无论它是 ArrayList<> 还是其他东西,然后调用 notifyDataSetChanged()您的适配器类的对象。

所以在这里看看我是如何在我的适配器中管理它的,见下文

public class CountryCodeListAdapter extends BaseAdapter implements OnItemClickListener{

private Context context;
private ArrayList<CountryDataObject> dObj;
private ViewHolder holder;
private Typeface itemFont;
private int selectedPosition=-1;
private ArrayList<CountryDataObject> completeList;

public CountryCodeListAdapter(Context context, ArrayList<CountryDataObject> dObj) {
    this.context = context;
    this.dObj=dObj;
    completeList=new  ArrayList<CountryDataObject>();
    completeList.addAll(dObj);
    itemFont=Typeface.createFromAsset(context.getAssets(), "CaviarDreams.ttf");
}

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

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

@Override
public long getItemId(int position) {
    return position;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
    if(view==null){
        holder = new ViewHolder();
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(R.layout.states_inflator_layout, null);
        holder.textView = ((TextView)view.findViewById(R.id.stateNameInflator));
        holder.checkImg=(ImageView)view.findViewById(R.id.checkBoxState);
        view.setTag(holder);
    }else{
        holder = (ViewHolder) view.getTag();
    }
    holder.textView.setText(dObj.get(position).getCountryName());
    holder.textView.setTypeface(itemFont);

    if(position==selectedPosition)
     {
         holder.checkImg.setImageResource(R.drawable.check);
     }
     else
     {
         holder.checkImg.setImageResource(R.drawable.uncheck);
     }
    return view;
}
private class ViewHolder{
    private TextView textView;
    private ImageView checkImg;
}

public void getFilter(String name) {
    dObj.clear();
    if(!name.equals("")){
    for (CountryDataObject item : completeList) {
        if(item.getCountryName().toLowerCase().startsWith(name.toLowerCase(),0)){
            dObj.add(item);
        }
    }
    }
    else {
        dObj.addAll(completeList);
    }
    selectedPosition=-1;
    notifyDataSetChanged();
    notifyDataSetInvalidated(); 
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
        long id) {
    Registration reg=(Registration)context;
    selectedPosition=position;
    reg.setSelectedCountryCode("+"+dObj.get(position).getCountryCode());
    notifyDataSetChanged();
}
}
于 2015-08-25T14:09:28.257 回答
1

考虑您已将列表传递给您的适配器。
利用:

list.getAdapter().notifyDataSetChanged()

更新您的列表。

于 2018-07-29T16:56:46.337 回答
1

只需myArrayList.remove(position);在侦听器内部使用:

  myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override public void onItemClick(AdapterView<?> parent, android.view.View view, int position, long id) {
           myArrayList.remove(position);
           myArrayAdapter.notifyDataSetChanged();
        }
    });
于 2018-02-13T22:33:12.077 回答
0

使用 SimpleCursorAdapter 时可以在适配器上调用 changeCursor(newCursor) 。

于 2014-02-06T18:17:40.803 回答
0

我有一个 ArrayList,我想在列表视图中显示它。ArrayList 包含来自 mysql 的元素。我重写了 onRefresh 方法,并在该方法中使用了 tablelayout.removeAllViews(); 然后重复该过程以再次从数据库中获取数据。但在此之前,请确保清除您的 ArrayList 或任何数据结构,否则新数据将附加到旧数据..

于 2015-03-03T17:04:00.353 回答
0

如果要从服务更新 UI 列表视图,请在 Main 活动中使适配器静态并执行以下操作:

@Override
public void onDestroy() {
    if (MainActivity.isInFront == true) {
        if (MainActivity.adapter != null) {
            MainActivity.adapter.notifyDataSetChanged();
        }

        MainActivity.listView.setAdapter(MainActivity.adapter);
    }
}    
于 2013-06-21T13:52:26.387 回答
0

我认为这取决于您所说的刷新是什么意思。您的意思是应该刷新 GUI 显示,还是应该刷新子视图,以便您可以通过编程方式调用 getChildAt(int) 并获取与适配器中的内容相对应的视图。

如果您希望刷新 GUI 显示,请在适配器上调用 notifyDataSetChanged()。下次重绘时将刷新 GUI。

如果您希望能够调用 getChildAt(int) 并获得反映适配器中内容的视图,则调用 layoutChildren()。这将导致从适配器数据重建子视图。

于 2015-01-19T15:29:22.530 回答
0

从列表视图中删除数据后,您必须调用refreshDrawableState(). 这是示例:

final DatabaseHelper db = new DatabaseHelper (ActivityName.this);

db.open();

db.deleteContact(arg3);

mListView.refreshDrawableState();

db.close();

和类中deleteContact的方法DatabaseHelper会有点像

public boolean deleteContact(long rowId) {

   return db.delete(TABLE_NAME, BaseColumns._ID + "=" + rowId, null) > 0;

}
于 2012-03-22T15:04:20.757 回答
0

当我想在一个片段中使用经过一段时间扫描的 BLE 设备的 mac 地址填充 ListView(在单个 TextView 中)时,我也是一样的。

我所做的是这样的:

public class Fragment01 extends android.support.v4.app.Fragment implements ...
{
    private ListView                listView;
    private ArrayAdapter<String>    arrayAdapter_string;

...

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    ...
    this.listView= (ListView) super.getActivity().findViewById(R.id.fragment01_listView);
    ...
    this.arrayAdapter_string= new ArrayAdapter<String>(super.getActivity(), R.layout.dispositivo_ble_item, R.id.fragment01_item_textView_titulo);
    this.listView.setAdapter(this.arrayAdapter_string);
}


@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
    ...
    super.getActivity().runOnUiThread(new RefreshListView(device));
}


private class RefreshListView implements Runnable
{
    private BluetoothDevice bluetoothDevice;

    public RefreshListView(BluetoothDevice bluetoothDevice)
    {
        this.bluetoothDevice= bluetoothDevice;
    }

    @Override
    public void run()
    {
        Fragment01.this.arrayAdapter_string.add(new String(bluetoothDevice.toString()));
        Fragment01.this.arrayAdapter_string.notifyDataSetChanged();
    }
}

然后 ListView 开始动态填充找到的设备的 mac 地址。

于 2014-09-15T23:52:07.430 回答
0

我无法让 notifyDataSetChanged() 来更新我的 SimpleAdapter,因此我尝试首先使用 removeAllViews() 删除所有附加到父布局的视图,然后添加 ListView,这很有效,允许我更新用户界面:

LinearLayout results = (LinearLayout)findViewById(R.id.results);
ListView lv = new ListView(this);
ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
SimpleAdapter adapter = new SimpleAdapter( this, list, R.layout.directory_row, 
                new String[] { "name", "dept" }, new int[] { R.id.name, R.id.dept } );

for (...) { 
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("name", name);
    map.put("dept", dept);
    list.add(map);
}

lv.setAdapter(adapter);
results.removeAllViews();     
results.addView(lv);
于 2013-08-22T16:38:27.243 回答
0

另一个选项是onWindowFocusChanged方法,但确定它很敏感,需要一些额外的编码来让感兴趣的人

 override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)

       // some controls needed
        programList = usersDBHelper.readProgram(model.title!!)
        notesAdapter = DailyAdapter(this, programList)
        notesAdapter.notifyDataSetChanged()
        listview_act_daily.adapter = notesAdapter
    }
于 2018-07-21T18:54:19.577 回答
0

如果您按照 android 指南进行操作,并且您正在使用ContentProviders从中获取数据,并且您正在使用andDatabase中显示它,那么您对相关数据的所有更改将自动反映在 ListView 中。ListViewCursorLoaderCursorAdapters

getContext().getContentResolver().notifyChange(uri, null);在光标ContentProvider上将足以反映更改。无需额外的工作。

但是当你不使用这些时,你需要在数据集发生变化时告诉适配器。您还需要重新填充/重新加载您的数据集(例如列表),然后您需要调用notifyDataSetChanged()适配器。

notifyDataSetChanged()如果数据集中没有更改,则不会工作。这是文档中方法上方的注释-

/**
 * Notifies the attached observers that the underlying data has been changed
 * and any View reflecting the data set should refresh itself.
 */
于 2015-10-31T16:32:55.350 回答
0

如果我在这里谈论我的场景,则上述答案都不起作用,因为我有显示 db 值列表以及删除按钮的活动,当按下删除按钮时,我想从列表中删除该项目。

很酷的是,我没有使用回收器视图,而是使用简单的列表视图和在适配器类中初始化的列表视图。因此,调用notifyDataSetChanged()不会在适配器类内部执行任何操作,甚至在初始化适配器对象的活动类中也不会执行任何操作,因为 delete 方法在适配器类中。

因此,解决方案是在适配器类getView方法中从适配器中删除对象(仅删除该特定对象,但如果要全部删除,请调用clear())。

让您了解一下,我的代码是什么样的,

public class WordAdapter extends ArrayAdapter<Word> {
  Context context;

  public WordAdapter(Activity context, ArrayList<Word> words) {}
    //.......

    @NonNull
    @Override
    public View getView(final int position, View convertView, ViewGroup group) {
      //.......
     ImageButton deleteBt = listItemView.findViewById(R.id.word_delete_bt);
     deleteBt.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             if (vocabDb.deleteWord(currentWord.id)) {
                //.....
             } else{
                //.....
             }
             remove(getItem(position)); // <---- here is the trick ---<
             //clear() // if you want to clear everything
         }
    });
 //....

注意:这里的remove()getItem()方法都是继承自Adapter类。

  • remove()- 删除被点击的特定项目
  • getItem(position)- 是从单击的位置获取项目(这里是我添加到列表中的 Word 对象)。

这就是我将适配器设置为活动类中的列表视图的方式,

    ArrayList<Word> wordList = new ArrayList();
    WordAdapter adapter = new WordAdapter(this, wordList);

    ListView list_view = (ListView) findViewById(R.id.activity_view_words);
    list_view.setAdapter(adapter);
于 2018-10-08T17:33:19.553 回答
0

我只能通过获取新的适配器数据来获取 notifyDataSetChanged,然后为列表视图重置适配器,然后像这样进行调用:

    expandableAdapter = baseFragmentParent.setupEXLVAdapter();
    baseFragmentParent.setAdapter(expandableAdapter);
    expandableAdapter.notifyDataSetChanged(); 
于 2016-03-11T21:11:09.967 回答
-1

最简单的方法是制作一个新的 Adaper 并删除旧的:

myListView.setAdapter(new MyListAdapter(...));
于 2017-11-28T15:45:52.787 回答