3

在我一直在努力的應用程序中,我有一個自定義類DeviceListAdapter擴展BaseAdapter它被傳遞到我的ListView。在我的DeviceListAdapter課堂上,我保留我自己的ArrayList<Device>,我用它來生成View getView(...)的列表視圖。每當應用程序導致數據更改時,我使用DeviceListAdapter中的自定義方法更新ArrayList<Device>以反映更改。我已經使用調試器和許多打印語句來檢查數據是否按預期的方式發生了變化,如指定的那樣添加和刪除Device對象。但是,在每次更改數據後,我還會調用notifyDataSetChanged(),但在UI上沒有任何元素會更新。在調試器中,我發現調用notifyDataSetChanged()之後,getView(...)方法未被調用,這就解釋了爲什麼ListView未被重繪。爲了弄清楚爲什麼,我使用調試器的「step into」函數來跟蹤程序執行進入android框架的位置,因爲我已經下載了SDK源代碼。我發現的非常有趣。執行的路徑是這樣的:Android的ListView適配器錯誤調用notifyDataSetChanged,Android的錯誤?

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

enter image description here enter image description here enter image description here enter image description here

而是調用onChanged()方法,它跳下軌道,一旦達到AbsListView執行onInvalidated()方法。最初我以爲這是一個錯誤的調試器可能讀錯了行號,但我重新啓動我的Android Studio以及完全卸載並重新安裝應用程序,但結果是一樣的。任何人都可以告訴我,如果這是Android框架的合法問題,或者如果調試器對於在自己的項目文件之外追蹤執行不可靠?

更多關於我的執行notifyDataSetChanged() ...我創建的本地方法重寫BaseAdapternotifyDataSetChanged(),這樣我可以設置一個布爾標誌mForceRedrawDeviceListAdapter內,以我是否應該強制重繪我的列表條目。在getView(...)方法中,我通常會檢查第二個參數View convertView是否爲空,如果是,則重繪視圖,如果不是,則傳遞convertView並返回它。但是,當'mForceRedraw'爲true時,我從不返回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; 
     } 
    } 
+0

你可以給你的應用程序代碼段? – Kushal

回答

1

你是在適配器和呼叫通知的數據集changed.This在理想情況下甚至不needed.Because你正在修改這是由內部使用的數據集您的適配器。無論何時需要呈現視圖,都會調用適配器的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; 
     } 
+0

非常感謝你,它非常完美!我一直認爲notifyDataSetChanged導致ListView重新掃描適配器,但我只是濫用convertView。 – nicholastmosher

1

有許多錯誤與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; 
} 
+0

我想我應該更具體。我對ListView的唯一修改是添加和刪除元素,但即使這樣做似乎也不會導致它們被重繪。我在上面添加了我的適配器的一個片段。有趣的是,當我將我的'mForceRedraw'設置爲true時,它似乎工作正常,但恐怕從不使用'convertView'是不好的做法。 – nicholastmosher

+0

哦,我想我誤解了你想要創造的真實點。我只是移動了if語句之外的數據填充方法,並且它工作正常。 – nicholastmosher

相關問題