Android ListView 适配器调用 notifyDataSetChanged 时出错,Android 错误?
Android ListView Adapter error calling notifyDataSetChanged, Android bug?
在我一直在开发的应用程序中,我有一个自定义 class DeviceListAdapter
扩展 BaseAdapter
,它被传递到我的 ListView
。在我的 DeviceListAdapter
class 中,我保留了自己的 ArrayList<Device>
,我用它来生成带有 View getView(... )
的列表视图。每当应用导致数据发生变化时,我都会使用 DeviceListAdapter
中的自定义方法来更新 ArrayList<Device>
以反映变化。我已经使用调试器和许多打印语句来检查数据是否按照我期望的方式更改,按照指定添加和删除 Device
对象。但是,每次更改数据后我也会调用 notifyDataSetChanged()
,但在 UI none 上的元素会得到更新。在调试器中,我发现在调用 notifyDataSetChanged()
之后,getView(... )
方法没有被调用,这解释了为什么 ListView
没有被重绘。为了找出原因,我使用调试器的 "step into" 函数来跟踪程序执行进入 android 框架的位置,因为我已经下载了 SDK 源代码。我的发现非常有趣。执行路径是这样的:
DeviceListAdapter.notifyDataSetChanged()
BaseAdapter.notifyDataSetChanged()
DataSetObservable.notifyChanged()
AbsListView.onInvalidated()
而不是调用 onChanged()
方法,它会在到达 AbsListView
时跳转并执行 onInvalidated()
方法。最初我认为这是调试器的错误,可能读取了错误的行号,但我重新启动了我的 Android Studio 并完全卸载并重新安装了应用程序,但结果是一样的。谁能告诉我这是否是 Android 框架的合理问题,或者调试器是否不可靠地跟踪我自己的项目文件之外的执行?
关于我的 notifyDataSetChanged()
实现的更多信息...我创建了本地方法来覆盖 BaseAdapter
的 notifyDataSetChanged()
,这样我就可以设置一个布尔标志 mForceRedraw
在我的 DeviceListAdapter
里面,关于我是否应该强制重绘我的列表条目。在 getView(... )
方法中,我通常检查第二个参数 View convertView
是否为 null,如果是,则重绘视图,如果不是,则传递 convertView 并 return 它。但是,当 'mForceRedraw' 为真时,我从不 return convertView
,我明确地重绘视图。出现的问题是我之前的顾虑造成的,就是我执行notifyDataSetChanged()
.
后没有调用getView()
编辑:这是我的 DeviceListAdapter
:
的代码片段
/**
* Serves data about current Device data to the mDeviceListView. Manages the dynamic and
* persistent storage of the configured Devices and constructs views of each individual
* list item for placement in the list.
*/
private class DeviceListAdapter extends BaseAdapter {
private boolean mForceRedraw = false;
/**
* Dynamic array that keeps track of all devices currently being managed.
* This is held in memory and is readily accessible so that system calls
* requesting View updates can be satisfied quickly.
*/
private List<Device> mDeviceEntries;
private Context mContext;
public DeviceListAdapter(Context context) {
this.mContext = context;
this.mDeviceEntries = new ArrayList<>();
populateFromStorage();
}
/**
* Inserts the given device into storage and notifies the mDeviceListView of a data update.
* @param newDevice The device to add to memory.
*/
public void put(Device newDevice) {
Preconditions.checkNotNull(newDevice);
boolean flagUpdatedExisting = false;
for (Device device : mDeviceEntries) {
if (newDevice.isVersionOf(device)) {
int index = mDeviceEntries.indexOf(device);
if(index != -1) {
mDeviceEntries.set(index, newDevice);
flagUpdatedExisting = true;
break;
} else {
throw new IllegalStateException();
}
}
//If an existing device was not updated, then this is a new device, add it to the list
if (!flagUpdatedExisting) {
mDeviceEntries.add(newDevice);
}
TECDataAdapter.setDevices(mDeviceEntries);
notifyDataSetChanged();
}
/**
* If the given device exists in storage, delete it and remove it from the mDeviceListView.
* @param device
*/
public void delete(Device device) {
Preconditions.checkNotNull(device);
//Remove device from mDeviceEntries
Iterator iterator = mDeviceEntries.iterator();
while(iterator.hasNext()) {
Device d = (Device) iterator.next();
if(device.isVersionOf(d)) {
iterator.remove();
}
}
TECDataAdapter.setDevices(mDeviceEntries);
notifyDataSetChanged();
}
/**
* Retrieves Device entries from persistent storage and loads them into the dynamic
* array responsible for displaying the entries in the listView.
*/
public void populateFromStorage() {
List<Device> temp = Preconditions.checkNotNull(TECDataAdapter.getDevices());
mDeviceEntries = temp;
notifyDataSetChanged();
}
public int getCount() {
if (mDeviceEntries != null) {
return mDeviceEntries.size();
}
return 0;
}
public Object getItem(int position) {
return mDeviceEntries.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
if (convertView == null || mForceRedraw) //Regenerate the view
{
/* Draws my views */
} else //Reuse the view
{
view = (LinearLayout) convertView;
}
return view;
}
@Override
public void notifyDataSetChanged() {
mForceRedraw = true;
super.notifyDataSetChanged();
mForceRedraw = false;
}
}
notifyDataSetChanged()
有很多错误,如果您尝试对列表数据进行一些复杂的工作,它们通常会出现。
主要是因为方法懒惰,无法区分变化,所以为了避免这个问题,用这个场景测试你的代码:
- 删除变化的行
- 致电
notifyDataSetChanged()
- 在其索引处添加更改的行
- 再次致电
notifyDataSetChanged()
然后,告诉我它是否没有解决您的问题。
编辑:放入适配器代码后,我看到了您代码中的缺陷。
抱歉回复晚了:
convertView
是您之前在初始化后填充的视图。
当在方法 getView()
中获得 convertView
的实例时,必须在返回之前填充它。
所以要清楚,做这样的事情:
public View getView(final int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) //Regenerate the view
{
/* Instantiate your view */
} else {
view = convertView;
}
// populate the elements in view like EditText, TextView, ImageView and etc
return view;
}
您在适配器中并且调用通知数据集 changed.This 理想情况下甚至不会 needed.Because 您 正在修改 您内部使用的数据集adapter.The 每当需要呈现视图时,总会调用适配器的 getView 方法。
convertView 方法是单独回收视图(而不是数据)。它只是为您提供了一种替代昂贵的视图处理的方法 inflation。
那么您的代码应该是什么:
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
if (convertView == null) //Regenerate the view
{
/* inflate Draws my views */
} else
{
view = (LinearLayout) convertView;
}
//repopulate this view with the data that needs to appear at this position using getItem(position)
return view;
}
在我一直在开发的应用程序中,我有一个自定义 class DeviceListAdapter
扩展 BaseAdapter
,它被传递到我的 ListView
。在我的 DeviceListAdapter
class 中,我保留了自己的 ArrayList<Device>
,我用它来生成带有 View getView(... )
的列表视图。每当应用导致数据发生变化时,我都会使用 DeviceListAdapter
中的自定义方法来更新 ArrayList<Device>
以反映变化。我已经使用调试器和许多打印语句来检查数据是否按照我期望的方式更改,按照指定添加和删除 Device
对象。但是,每次更改数据后我也会调用 notifyDataSetChanged()
,但在 UI none 上的元素会得到更新。在调试器中,我发现在调用 notifyDataSetChanged()
之后,getView(... )
方法没有被调用,这解释了为什么 ListView
没有被重绘。为了找出原因,我使用调试器的 "step into" 函数来跟踪程序执行进入 android 框架的位置,因为我已经下载了 SDK 源代码。我的发现非常有趣。执行路径是这样的:
DeviceListAdapter.notifyDataSetChanged()
BaseAdapter.notifyDataSetChanged()
DataSetObservable.notifyChanged()
AbsListView.onInvalidated()
而不是调用 onChanged()
方法,它会在到达 AbsListView
时跳转并执行 onInvalidated()
方法。最初我认为这是调试器的错误,可能读取了错误的行号,但我重新启动了我的 Android Studio 并完全卸载并重新安装了应用程序,但结果是一样的。谁能告诉我这是否是 Android 框架的合理问题,或者调试器是否不可靠地跟踪我自己的项目文件之外的执行?
关于我的 notifyDataSetChanged()
实现的更多信息...我创建了本地方法来覆盖 BaseAdapter
的 notifyDataSetChanged()
,这样我就可以设置一个布尔标志 mForceRedraw
在我的 DeviceListAdapter
里面,关于我是否应该强制重绘我的列表条目。在 getView(... )
方法中,我通常检查第二个参数 View convertView
是否为 null,如果是,则重绘视图,如果不是,则传递 convertView 并 return 它。但是,当 'mForceRedraw' 为真时,我从不 return convertView
,我明确地重绘视图。出现的问题是我之前的顾虑造成的,就是我执行notifyDataSetChanged()
.
getView()
编辑:这是我的 DeviceListAdapter
:
/**
* Serves data about current Device data to the mDeviceListView. Manages the dynamic and
* persistent storage of the configured Devices and constructs views of each individual
* list item for placement in the list.
*/
private class DeviceListAdapter extends BaseAdapter {
private boolean mForceRedraw = false;
/**
* Dynamic array that keeps track of all devices currently being managed.
* This is held in memory and is readily accessible so that system calls
* requesting View updates can be satisfied quickly.
*/
private List<Device> mDeviceEntries;
private Context mContext;
public DeviceListAdapter(Context context) {
this.mContext = context;
this.mDeviceEntries = new ArrayList<>();
populateFromStorage();
}
/**
* Inserts the given device into storage and notifies the mDeviceListView of a data update.
* @param newDevice The device to add to memory.
*/
public void put(Device newDevice) {
Preconditions.checkNotNull(newDevice);
boolean flagUpdatedExisting = false;
for (Device device : mDeviceEntries) {
if (newDevice.isVersionOf(device)) {
int index = mDeviceEntries.indexOf(device);
if(index != -1) {
mDeviceEntries.set(index, newDevice);
flagUpdatedExisting = true;
break;
} else {
throw new IllegalStateException();
}
}
//If an existing device was not updated, then this is a new device, add it to the list
if (!flagUpdatedExisting) {
mDeviceEntries.add(newDevice);
}
TECDataAdapter.setDevices(mDeviceEntries);
notifyDataSetChanged();
}
/**
* If the given device exists in storage, delete it and remove it from the mDeviceListView.
* @param device
*/
public void delete(Device device) {
Preconditions.checkNotNull(device);
//Remove device from mDeviceEntries
Iterator iterator = mDeviceEntries.iterator();
while(iterator.hasNext()) {
Device d = (Device) iterator.next();
if(device.isVersionOf(d)) {
iterator.remove();
}
}
TECDataAdapter.setDevices(mDeviceEntries);
notifyDataSetChanged();
}
/**
* Retrieves Device entries from persistent storage and loads them into the dynamic
* array responsible for displaying the entries in the listView.
*/
public void populateFromStorage() {
List<Device> temp = Preconditions.checkNotNull(TECDataAdapter.getDevices());
mDeviceEntries = temp;
notifyDataSetChanged();
}
public int getCount() {
if (mDeviceEntries != null) {
return mDeviceEntries.size();
}
return 0;
}
public Object getItem(int position) {
return mDeviceEntries.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
if (convertView == null || mForceRedraw) //Regenerate the view
{
/* Draws my views */
} else //Reuse the view
{
view = (LinearLayout) convertView;
}
return view;
}
@Override
public void notifyDataSetChanged() {
mForceRedraw = true;
super.notifyDataSetChanged();
mForceRedraw = false;
}
}
notifyDataSetChanged()
有很多错误,如果您尝试对列表数据进行一些复杂的工作,它们通常会出现。
主要是因为方法懒惰,无法区分变化,所以为了避免这个问题,用这个场景测试你的代码:
- 删除变化的行
- 致电
notifyDataSetChanged()
- 在其索引处添加更改的行
- 再次致电
notifyDataSetChanged()
然后,告诉我它是否没有解决您的问题。
编辑:放入适配器代码后,我看到了您代码中的缺陷。
抱歉回复晚了:
convertView
是您之前在初始化后填充的视图。
当在方法 getView()
中获得 convertView
的实例时,必须在返回之前填充它。
所以要清楚,做这样的事情:
public View getView(final int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) //Regenerate the view
{
/* Instantiate your view */
} else {
view = convertView;
}
// populate the elements in view like EditText, TextView, ImageView and etc
return view;
}
您在适配器中并且调用通知数据集 changed.This 理想情况下甚至不会 needed.Because 您 正在修改 您内部使用的数据集adapter.The 每当需要呈现视图时,总会调用适配器的 getView 方法。
convertView 方法是单独回收视图(而不是数据)。它只是为您提供了一种替代昂贵的视图处理的方法 inflation。
那么您的代码应该是什么:
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
if (convertView == null) //Regenerate the view
{
/* inflate Draws my views */
} else
{
view = (LinearLayout) convertView;
}
//repopulate this view with the data that needs to appear at this position using getItem(position)
return view;
}