【问题标题】:Android ListView Adapter error calling notifyDataSetChanged, Android bug?Android ListView Adapter 错误调用 notifyDataSetChanged,Android 错误?
【发布时间】:2015-04-22 18:40:31
【问题描述】:

在我一直在开发的应用程序中,我有一个自定义类 DeviceListAdapter 扩展 BaseAdapter,它会传递给我的 ListView。在我的DeviceListAdapter 类中,我保留了自己的ArrayList<Device>,我用它来生成带有View getView(... ) 的列表视图。每当应用程序导致数据发生更改时,我都会使用DeviceListAdapter 中的自定义方法来更新ArrayList<Device> 以反映更改。我使用调试器和许多打印语句来检查数据是否按照我的预期进行了更改,按照指定添加和删除 Device 对象。但是,在每次更改数据后,我也会调用notifyDataSetChanged(),但在 UI 上没有任何元素得到更新。在调试器中,我发现调用notifyDataSetChanged() 后,getView(... ) 方法没有被调用,这就解释了为什么ListView 没有被重绘。为了找出原因,我使用调试器的“步入”功能来跟踪程序执行进入 android 框架的位置,因为我已经下载了 SDK 源代码。我发现非常有趣。执行路径是这样的:

DeviceListAdapter.notifyDataSetChanged()
BaseAdapter.notifyDataSetChanged()
DataSetObservable.notifyChanged()
AbsListView.onInvalidated()

而不是调用onChanged() 方法,它跳过轨道并在到达AbsListView 时执行onInvalidated() 方法。最初我认为这是调试器错误,可能读取了错误的行号,但我重新启动了我的 Android Studio 并完全卸载并重新安装了应用程序,但结果是一样的。谁能告诉我这是否是 Android 框架的合法问题,或者调试器在我自己的项目文件之外跟踪执行是否不可靠?

更多关于我的notifyDataSetChanged() 实现...我创建了本地方法来覆盖BaseAdapternotifyDataSetChanged(),这样我就可以在DeviceListAdapter 内部设置一个布尔标志mForceRedraw 是否我应该强制重绘我的列表条目。在getView(... ) 方法中,我通常检查第二个参数View convertView 是否为空,如果是,则重绘视图,如果不是,则传递convertView 并返回它。但是,当 'mForceRedraw' 为真时,我从不返回 convertView,我明确地重绘视图。出现的问题是我之前的顾虑造成的,就是我执行notifyDataSetChanged()后没有调用getView()

编辑:这是我的DeviceListAdapter 的代码 sn-p:

    /**
     * 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;
        }
    }

【问题讨论】:

  • 你能提供你的应用程序代码的sn-p吗?

标签: java android android-listview baseadapter notifydatasetchanged


【解决方案1】:

您在适配器中并调用通知数据集已更改。理想情况下甚至不需要。因为您正在修改适配器内部使用的数据集。适配器的 getView 方法将总是在需要渲染视图时调用。

convertView 方法是仅回收视图(而不是数据)。它只是为您提供一种替代昂贵的视图膨胀过程的方法。

那么你的代码应该是什么:

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

【讨论】:

  • 非常感谢,它运行良好!我一直以为 notifyDataSetChanged 导致 ListView 重新扫描适配器,但我只是误用了 convertView。
【解决方案2】:

notifyDataSetChanged() 存在许多错误,如果您尝试对列表数据进行一些复杂的工作,通常会出现这些错误。

多半是因为方法是惰性的,无法区分变化,所以为了避免这个问题,用这个场景测试你的代码:

  1. 删除更改的行
  2. 致电notifyDataSetChanged()
  3. 在其索引处添加更改的行
  4. 再次致电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;
}

【讨论】:

  • 我想我应该更具体一些。我对ListView 所做的唯一更改是添加和删除元素,但即使这样似乎也不会导致它们被重绘。我在上面添加了我的适配器的 sn-p。有趣的是,当我将 mForceRedraw 设置为 true 时,它​​似乎确实有效,但恐怕永远不要使用 convertView 是不好的做法。
  • 哦,我想我误解了你想要表达的真正意思。我只是将数据填充方法移到 if 语句之外,它就起作用了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多