ListView
添加/删除动态数据后如何刷新Android ?
25 回答
修改该适配器中的数据后调用notifyDataSetChanged()
您的对象。Adapter
notifyDataSetChanged()
可以在此 Google I/O 视频中查看有关如何/何时调用的一些其他细节。
你也可以使用这个:
myListView.invalidateViews();
请忽略此问题的所有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
随时调用 runnable :
runOnUiThread(run);
OnCreate()
,你设置你的可运行线程:
run = new Runnable() {
public void run() {
//reload content
arraylist.clear();
arraylist.addAll(db.readAll());
adapter.notifyDataSetChanged();
listview.invalidateViews();
listview.refreshDrawableState();
}
};
我在动态刷新列表视图时遇到了一些问题。
在适配器上调用 notifyDataSetChanged()。
可以在此 Google I/O 视频中查看有关如何/何时调用 notifyDataSetChanged() 的一些其他细节。
notifyDataSetChanged() 在我的情况下无法正常工作[我从另一个类调用 notifyDataSetChanged]。就在我在正在运行的活动(线程)中编辑 ListView 的情况下。感谢克里斯托弗的视频给出了最后的提示。
在我的第二堂课中,我使用
Runnable run = new Runnable(){
public void run(){
contactsActivity.update();
}
};
contactsActivity.runOnUiThread(run);
从我的活动中访问更新()。本次更新包括
myAdapter.notifyDataSetChanged();
告诉适配器刷新视图。据我所知,工作得很好。
如果您使用SimpleCursorAdapter ,请尝试在 Cursor 对象上调用requery() 。
如果您对 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();
这篇文章中人们提出的解决方案是否有效主要取决于您设备的 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!!!
如果你想在刷新时保持滚动位置,你可以这样做:
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:刷新时保持滚动位置。
对我来说,在 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();
希望它对你有意义。
您需要使用该列表中的单个对象,该对象是您正在膨胀的数据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();
}
}
考虑您已将列表传递给您的适配器。
利用:
list.getAdapter().notifyDataSetChanged()
更新您的列表。
只需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();
}
});
使用 SimpleCursorAdapter 时可以在适配器上调用 changeCursor(newCursor) 。
我有一个 ArrayList,我想在列表视图中显示它。ArrayList 包含来自 mysql 的元素。我重写了 onRefresh 方法,并在该方法中使用了 tablelayout.removeAllViews(); 然后重复该过程以再次从数据库中获取数据。但在此之前,请确保清除您的 ArrayList 或任何数据结构,否则新数据将附加到旧数据..
如果要从服务更新 UI 列表视图,请在 Main 活动中使适配器静态并执行以下操作:
@Override
public void onDestroy() {
if (MainActivity.isInFront == true) {
if (MainActivity.adapter != null) {
MainActivity.adapter.notifyDataSetChanged();
}
MainActivity.listView.setAdapter(MainActivity.adapter);
}
}
我认为这取决于您所说的刷新是什么意思。您的意思是应该刷新 GUI 显示,还是应该刷新子视图,以便您可以通过编程方式调用 getChildAt(int) 并获取与适配器中的内容相对应的视图。
如果您希望刷新 GUI 显示,请在适配器上调用 notifyDataSetChanged()。下次重绘时将刷新 GUI。
如果您希望能够调用 getChildAt(int) 并获得反映适配器中内容的视图,则调用 layoutChildren()。这将导致从适配器数据重建子视图。
从列表视图中删除数据后,您必须调用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;
}
当我想在一个片段中使用经过一段时间扫描的 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 地址。
我无法让 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);
另一个选项是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
}
如果您按照 android 指南进行操作,并且您正在使用ContentProviders
从中获取数据,并且您正在使用andDatabase
中显示它,那么您对相关数据的所有更改将自动反映在 ListView 中。ListView
CursorLoader
CursorAdapters
您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.
*/
如果我在这里谈论我的场景,则上述答案都不起作用,因为我有显示 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);
我只能通过获取新的适配器数据来获取 notifyDataSetChanged,然后为列表视图重置适配器,然后像这样进行调用:
expandableAdapter = baseFragmentParent.setupEXLVAdapter();
baseFragmentParent.setAdapter(expandableAdapter);
expandableAdapter.notifyDataSetChanged();
最简单的方法是制作一个新的 Adaper 并删除旧的:
myListView.setAdapter(new MyListAdapter(...));