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