簡體   English   中英

Android ListView Adapter錯誤調用notifyDataSetChanged,Android bug?

[英]Android ListView Adapter error calling notifyDataSetChanged, Android bug?

在我一直在努力的應用程序中,我有一個自定義類DeviceListAdapter擴展BaseAdapter ,它被傳遞給我的ListView 在我的DeviceListAdapter類中,我保留了自己的ArrayList<Device> ,我使用它來使用View getView(... )生成列表的視圖。 每當應用程序導致數據更改時,我都會使用DeviceListAdapter自定義方法更新ArrayList<Device>以反映更改。 我已經使用調試器和許多print語句檢查數據是否按照我預期的方式更改,添加和刪除指定的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是否為null,如果是,那么我重繪視圖,如果沒有,那么我通過convertView並返回它。 但是,當'mForceRedraw'為真時,我永遠不會返回convertView ,我明確地重繪了視圖。 出現的問題是由我之前的關注引起的,即我執行notifyDataSetChanged()后沒有調用getView() notifyDataSetChanged()

編輯:這是我的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()存在許多錯誤,如果您嘗試對列表數據進行一些復雜的工作,通常會出現這些錯誤。

大多數情況下,這是因為該方法是惰性的,無法區分更改,因此要避免此問題,請使用以下方案測試代碼:

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

您在適配器和調用通知數據集已更改 。理想情況下甚至不需要。因為您正在修改適配器內部使用的數據集。只要需要呈現視圖,就會始終調用適配器的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;
        }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM