如何刷新Android ListView?


Answers:


530

调用notifyDataSetChanged()您的Adapter对象,一旦你在该适配器修改的数据。

notifyDataSetChanged()可以在此Google I / O视频中查看有关呼叫方式/呼叫时间的其他一些细节。


24
您应该在UI线程上运行它。在UI线程中创建处理程序,然后向其发布Runable
Kirill

11
再一次:notifyDataSetChanged()至少对于推荐的使用实践不起作用(建议不要阅读)。如果使用listview,游标适配器和内容提供程序,则可以尝试以下操作:gettLoaderManager()。restartLoader()。请参阅:stackoverflow.com/a/19657500/1087411我希望至少看到有关notifyDataSetChanged()的一些文档。在android中,还有很多要进行黑盒测试。
Anderson

如果notifyDataSetChanged无法正常工作,则说明您做错了
zdarsky.peter 2014年

4
notifyDataSetChanged并不总是有效。我不确定为什么会这样。有时您必须调用listView.setAdapter(adapter)清除旧视图。另请参阅:stackoverflow.com/a/16261588/153275

1
我从没能使notifyDataSetChanged工作。我可以清楚地看到,我提供给适配器的集合已更改(元素已删除),但notifyDataSetChanged从未执行任何操作。不确定此方法是否已损坏?
byemute

204

您也可以使用此:

myListView.invalidateViews();

22
如果只需要重新绘制视图,这是正确的答案。例如,发生用户登录后,需要在listView的每个视图中公开更多信息或选项。这就是我需要做的,所以投了赞成票。如果基础数据发生更改,则最好使用notifyDataSetChanged()。
SBerg413

实际上invalidateViews()在源代码集中,mDataChanged = true;所以我不确定与notifyDataSetChanged()
vovahost

你救了我的一天,伙计!
oskarko '16

您可以采用“可观察的列表”的列表类型,无需通知数据集已更改
Priya

153

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

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

故障排除

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

如果这只是一个集合,则您要保留在内存中,然后在调用之前检查是否已从集合中实际删除或添加了项目notifyDataSetChanged()

如果您使用的是数据库或服务后端,则必须在调用之前调用该方法以再次检索信息(或处理内存中的数据)notifyDataSetChanged()

东西是这个 notifyDataSetChanged仅在数据集已更改的情况下有效。因此,如果找不到更改,那就可以在这里查找。如果需要调试。

ArrayAdapter与BaseAdapter

我确实发现,使用适配器可以管理集合,就像使用BaseAdapter更好。某些适配器(例如ArrayAdapter)已经管理了自己的集合,这使得更难到达正确的集合进行更新。在大多数情况下,这实际上只是不必要的额外难度。

UI线程

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

快速示例项目

可以在https://github.com/hanscappelle/so-2250770.git中找到。只需克隆并在Android Studio中打开项目(渐变)。这个项目的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


16
你错了。notifyDataSetChanged()不适用于我,但是invalidateViews()正确。
babay 2013年

6
我遇到的问题是列表中的实际元素不会更改,例如元素数量相同。但是问题在于它们的呈现方式需要改变。这意味着我的适配器用于构建UI的代码需要再次运行,然后正确隐藏列表行中的某些元素。notifyDataSetChanged()不起作用...
Sven Haiges

9
这个答案确实帮助了我。由于某些功能原因,我将阵列保留在内存中。为了解决这个问题我现在打电话adapter.clear(),和adapter.addAll(Array<T>)之前调用notifyDataSetChanged()
迈克尔DePhillips

2
@hcpl,如果我在适配器的CheckBox的OnClickListener中,应该如何调用notifyDataSetChanged?invalidateViews正在立即完成工作。为什么invalidateViews调用方法错误?
2014年

1
我从没能使notifyDataSetChanged工作。我可以清楚地看到,我提供给适配器的集合已更改(元素已删除),但notifyDataSetChanged从未执行任何操作。不确定此方法是否已损坏?
byemute

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

1
在我需要基于TCP连接(来自“ nonUI”线程)的数据来更新列表视图的情况下,也可以使用。重新绘制列表的唯一方法是清除并重新添加ArrayList的所有元素。无论如何,如果您已经在UI线程中,则不需要通过runOnUiThread()函数调用代码。
安德烈(Andre)2015年

谢谢!这个对我有用。对于谁希望从碎片运行这一切的人,你应该使用getActivity()runOnUiThread(新的Runnable ...以确保代码在UI线程中运行。
codingpuss

我看不到它的目的,run = new Runnable()只是给我的函数提供了public void refreshUIThread()一种对您的方法进行建模的run()方法(也可以使用)。
T.Woody '18

11

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

在适配器上调用notifyDataSetChanged()。

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

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

在我第二节课中

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

从我的活动中访问update()。此更新包括

myAdapter.notifyDataSetChanged();

告诉适配器刷新视图。就我所能说的,还不错。



5

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

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

.......
......
做你的CRUD操作..
......
.....
DBAdapter.open();
DBAdapter.insert_into_SingleList();
//带来该DB_results并将其添加到列表中作为其内容...。
                                            ls2.setAdapter(new ArrayAdapter(DynTABSample.this,
    android.R.layout.simple_list_item_1,DBAdapter.DB_ListView));
                                            DBAdapter.close();

2

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

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

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

ResultGpRowAdapter.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!!!

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:刷新时保持滚动位置


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

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

1
您的解释被低估了,但这确实使我对arraylist参考有所了解。谢谢
亚伯拉罕·普特拉·普拉卡萨

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;

}

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

0

对我来说,更改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();

希望这对您有意义。



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地址动态填充。


0

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

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

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


0

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


0

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

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

        MainActivity.listView.setAdapter(MainActivity.adapter);
    }
}    

0

如果您要按照android的指导方针进行操作,并且正在使用ContentProviders来获取数据,Database并且正在ListView使用CursorLoader和显示CursorAdapters,那么你对相关数据的所有更改会自动反映在ListView。

getContext().getContentResolver().notifyChange(uri, null);在光标上ContentProvider将足以反映更改。无需进行其他操作。

但是,当您不使用所有这些功能时,则需要告诉适配器数据集何时更改。另外,您需要重新填充/重新加载数据集(例如列表),然后需要调用notifyDataSetChanged()适配器。

notifyDataSetChanged()如果数据集没有更改,则将无法工作。这是docs-中方法上方的注释-

/**
 * Notifies the attached observers that the underlying data has been changed
 * and any View reflecting the data set should refresh itself.
 */

0

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

    expandableAdapter = baseFragmentParent.setupEXLVAdapter();
    baseFragmentParent.setAdapter(expandableAdapter);
    expandableAdapter.notifyDataSetChanged(); 

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
    }

0

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

list.getAdapter().notifyDataSetChanged()

更新您的列表。


0

如果我在这里谈论我的情况,那么以上答案都不可行,因为我有活动显示db值列表以及一个删除按钮,并且当按下删除按钮时,我想从列表中删除该项目。

很棒的事情是,我没有使用回收器视图,而是使用了一个简单的列表视图,并且该列表视图在适配器类中进行了初始化。因此,notifyDataSetChanged()在适配器类内部,甚至在初始化适配器对象的活动类中,调用the方法都不会做任何事情,因为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对象)。

这就是我将适配器设置为活动类中的listview的方式,

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

-1

最简单的方法是制作新的Adaper并丢弃旧的Adaper:

myListView.setAdapter(new MyListAdapter(...));

这会消耗更多的内存。
Player1

将收集垃圾
user1712200 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.