如何刷新Android列表视图?

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

539023 次浏览

一旦修改了适配器中的数据,就在Adapter对象上调用notifyDataSetChanged()

关于如何/何时调用notifyDataSetChanged()的一些附加细节可以在这个谷歌I/O视频中查看。

如果你仍然不满意ListView刷新,你可以看看这个片段,这是从DB加载ListView,实际上你要做的就是简单地重新加载ListView,在你执行任何CRUD操作后 这不是一个最好的编码方式,但它会刷新ListView如你所愿.

它为我工作....如果你有更好的解决方案,请分享…

.......
......
do your CRUD Operations..
......
.....
DBAdapter.open();
DBAdapter.insert_into_SingleList();
// Bring that DB_results and add it to list as its contents....
ls2.setAdapter(new ArrayAdapter(DynTABSample.this,
android.R.layout.simple_list_item_1,                        DBAdapter.DB_ListView));
DBAdapter.close();

如果你正在使用SimpleCursorAdapter,尝试在Cursor对象上调用重新查询()

我有一些问题与动态刷新我的列表视图。

在适配器上调用notifyDataSetChanged()。

关于如何/何时调用notifyDataSetChanged()的其他细节可以在这个谷歌I/O视频中查看。

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

在我的第二节课上,我用了

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

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

myAdapter.notifyDataSetChanged();

告诉适配器刷新视图。

你也可以用这个:

myListView.invalidateViews();
从列表视图中删除数据后,必须调用refreshDrawableState()。 示例如下:

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


db.open();


db.deleteContact(arg3);


mListView.refreshDrawableState();


db.close();

DatabaseHelper类中的deleteContact方法将有点像

public boolean deleteContact(long rowId) {


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


}

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

正确的做法(幸运的是也标记为正确答案)是在适配器上调用notifyDataSetChanged()

故障排除

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

如果这只是一个保存在内存中的集合,则在调用notifyDataSetChanged()之前检查是否实际从集合中删除或向集合中添加了项。

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

问题是,这个notifyDataSetChanged只在数据集发生变化时有效。所以如果你没有发现变化,那就是你要看的地方。必要时进行调试。

ArrayAdapter vs BaseAdapter

我确实发现使用让您管理集合的适配器(如BaseAdapter)工作得更好。一些适配器(如ArrayAdapter)已经管理了自己的集合,因此很难获得适当的集合进行更新。在大多数情况下,这只是一个不必要的额外难度。

UI线程

的确,这个必须从UI线程调用。其他答案有如何实现这一目标的例子。然而,只有当你在UI线程之外处理这些信息时,才需要这样做。它来自服务或非UI线程。在简单的情况下,您将通过单击按钮或其他活动/片段更新数据。仍然在UI线程中。不需要总是弹出runOnUiTrhead。

快速示例项目

可以在https://github.com/hanscappelle/so-2250770.git找到。只需克隆并在Android Studio (gradle)中打开项目。这个项目有一个mainactitivity用所有随机数据构建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

如果你想从一个服务中更新UI listview,那么在你的Main活动中使适配器静态,并这样做:

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


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

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

希望你能理解。

我不能得到notifyDataSetChanged()来更新我的SimpleAdapter,所以我试着先删除所有的视图,使用removeAllViews()附加到父布局,然后添加ListView,这是有效的,允许我更新UI:

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

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

你可以随时调用runnable:

runOnUiThread(run);

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

run = new Runnable() {
public void run() {
//reload content
arraylist.clear();
arraylist.addAll(db.readAll());
adapter.notifyDataSetChanged();
listview.invalidateViews();
listview.refreshDrawableState();
}
};

本文中提出的解决方案是否有效主要取决于你的Android设备版本。例如,使用AddAll方法,你必须把android:minSdkVersion="10"在你的android设备。

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

我的代码:使用我自己的数据类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);
}
}

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

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

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

我也是一样,在一个片段中,我想用一段时间内扫描的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地址。

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

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

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

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

你需要使用列表中的一个对象,你要在ListView上扩展它的数据。当你从列表视图中删除元素时,也要从你正在使用的列表中删除它们,不管它是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();
}
}

如果你是按照android指南来操作的,你正在使用ContentProvidersDatabase中获取数据,并且你正在使用CursorLoaderCursorAdapters将其显示在ListView中,那么你对相关数据的所有更改将自动反映在ListView中。

你在ContentProvider中的光标上的getContext().getContentResolver().notifyChange(uri, null);将足以反映这些变化。不需要额外的工作。

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

__abc0将不会工作,如果在数据集中没有变化。 下面是docs中方法上面的注释-

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

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

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

最简单的方法是创建一个新的Adaper,并删除旧的Adaper:

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

在监听器中使用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();
}
});

其他选项是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
}

认为您已经将一个列表传递给适配器 用途:< br > < / p >

list.getAdapter().notifyDataSetChanged()

更新您的列表。

如果我在这里谈论我的场景,上面的答案都不会起作用,因为我有一个显示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) -是获取item(在这里,这是我的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);