【问题标题】:Android: SortedList with duplicatesAndroid:带有重复项的 SortedList
【发布时间】:2015-10-29 11:29:26
【问题描述】:

我在理解RecyclerViews SortedList时有些问题。

假设我有一个非常简单的类,只有一个非常简单的类保存数据:

public class Pojo {
    public final int id;
    public final char aChar;

    public Pojo(int id, char aChar) {
        this.id = id;
        this.aChar = aChar;
    }

    @Override
    public String toString() {
        return "Pojo[" + "id=" + id
                + ",aChar=" + aChar
                + "]";
    }
}

我的理解是排序后的列表不会包含任何重复项。

但是当我有一个带有这样回调的 SortedList 时:

....

@Override
public boolean areContentsTheSame(Pojo oldItem, Pojo newItem) {
    return oldItem.aChar == newItem.aChar;
}

@Override
public int compare(Pojo o1, Pojo o2) {
    return Character.compare(o1.aChar, o2.aChar);
}

@Override
public boolean areItemsTheSame(Pojo item1, Pojo item2) {
    return item1.id == item2.id;
}

当我添加多个具有相同 id 但不同字符的项目时,我最终会出现重复。

sortedList.add(new Pojo(1, 'a'));
sortedList.add(new Pojo(1, 'b'));

我希望列表能够更新项目。相反,即使areItemsTheSame 返回了true,我现在也有多个项目。

【问题讨论】:

标签: java android android-recyclerview sortedlist


【解决方案1】:

在 Java 中,不包含重复元素的集合是 Set。常见的实现类是HashSetTreeSet。假设 SortedList 这样做是错误的。

【讨论】:

  • 但是this 没有暗示吗?
  • 是的,我看起来很困惑。通常 List 可以包含重复项,但 Set 不能。可以试试用 SortedSet 代替 SortedList 吗?
  • 它是一个 android SortedList,而不是 utils.java。没有用于与 recyclerview 绑定的 SortedSet。
【解决方案2】:

我认为你应该在 compare 方法中使用 Integer.compare(o1.id, o2.id);,这是 SortList 决定这两个项目是否相同的地方。

【讨论】:

  • 从文档中清楚地说明这是为了订购。而且我认为如果> 2个项目相同或不同,则在areItemsTheSame()中决定
  • 好吧,我认为compareareItemsTheSame这两种方法就像hashCodeequal,如果你想让2个对象有相同的身份,他们必须首先有相同的hashCode,然后equal 返回真。所以在这种情况下,compare == 0 必须先出现,然后areItemsTheSame 才为真。
【解决方案3】:

SortedList 不保留任何 id 映射(因为 API 中没有 id)。 因此,当排序条件发生变化时(在您的情况下为 a 到 b),SortedList 无法找到现有元素。

你可以自己保留id映射,然后你的add方法如下:

void add(Item t) {
  Item existing = idMap.get(t.id);
  if (existing == null) {        
     sortedList.add(t);
  } else {
     sortedList.updateItemAt(sortedList.indexOf(existing), t);
  }
  idMap.put(t.id, t);
}

您还需要实现一个 remove 方法来从 idMap 中删除该项目。

【讨论】:

  • 正如您所指出的,当排序条件发生变化时,SortedList 找不到现有元素,indexOf 怎么能找到正确的索引?就我而言,它总是返回-1
  • 您需要在“更改数据之前”调用它。
【解决方案4】:

正如 Minhtdh 在他的回答中已经提到的那样,问题在于您的 compare()

看,add() 使用您实现的compare() 查找现有对象的索引。因此,当您的 compare() 返回 0 以外的值时,它会将对象添加到列表中。

在比较内容之前,您需要检查项目是否相同。但是,如果您的内容可以相同,则需要进行二次比较。

这就是我在你的情况下实现compare() 的方式:

@Override
public int compare(Pojo o1, Pojo o2) {
    int result;
    if (areItemsTheSame(o1, o2) {
        result = 0;
    } else {
        result = Character.compare(o1.aChar, o2.aChar);
        if (result == 0) {
            // TODO implement a secondary comparison 
        }
    }

    return result;
}

【讨论】:

    【解决方案5】:

    您可以通过这样做检查对象是否已存在于排序列表中。

    if (sortedList.indexOf(item) == -1) 
    {
        sortedList.add(item);  //Item still does not exist because index is -1
    } 
    else 
    {
        sortedList.updateItemAt(sortedList.indexOf(item), item);
    }
    

    【讨论】:

      【解决方案6】:

      我在创建聊天应用程序时遇到了类似的问题,我需要按他们的 ID 更新消息并按日期对消息进行排序。支持库的 SortedList 没有这样做,或者至少我没有时间深入研究它的源代码和测试。所以,我创建了一个小组件,MultiSortedList:

      import android.support.v7.widget.RecyclerView
      
      /**
       * Created by abduaziz on 6/14/18.
       *
       *   MultiSortedList is a wrapper component to ArrayList that keeps its elements in a sorted order
       *   using UpdateCallbackInterface. It is intended to be used inside recycler view adapters.
       *
       * */
      
      class MultiSortedList<T>(var updateCallback: UpdateCallback<T>, var adapter: RecyclerView.Adapter<*>? = null) {
      
          companion object {
              val TAG = "SORTEDLIST"
          }
      
          // internal list to hold elements by sortBy() -> visible to user
          private val list: ArrayList<T> = arrayListOf()
      
          // internal list to hold elements by updateBy() -> not visible
          private val uList: ArrayList<T> = arrayListOf()
      
          // add adapter from ui
          fun addAdapter(adapter: RecyclerView.Adapter<*>?) {
              this.adapter = adapter
          }
      
          /*
          * 1. Search for existing element that satisfies updateBy()
          * 2. Remove the existing element if found
          * 3. Add the new item with sortBy()
          * 4. Notify if adapter is not null
          * */
          fun add(newItem: T) {
              remove(newItem)
      
              // save to internal list by updateBy()
              var toBeStoredPosition = uList.binarySearch { updateCallback.updateBy(it, newItem) }
              if (toBeStoredPosition < 0) toBeStoredPosition = -(toBeStoredPosition + 1)
              uList.add(toBeStoredPosition, newItem)
      
              // save to UI list and notify changes
              var sortPosition = list.binarySearch { updateCallback.sortBy(it, newItem) }
              if (sortPosition < 0) sortPosition = -(sortPosition + 1)
              list.add(sortPosition, newItem)
              adapter?.notifyItemInserted(sortPosition)
          }
      
          /*
          * Remove and notify the adapter
          * */
          fun remove(removeItem: T) {
              val storedElementPosition = uList.binarySearch { updateCallback.updateBy(it, removeItem) }
              if (storedElementPosition >= 0 && storedElementPosition < uList.size) {
      
                  // remove from internal list
                  val itemTobeRemoved = uList[storedElementPosition]
                  uList.removeAt(storedElementPosition)
      
                  // remove from ui
                  val removePosition = list.binarySearch { updateCallback.sortBy(it, itemTobeRemoved) }
                  if (removePosition >= 0 && removePosition < list.size) {
                      list.removeAt(removePosition)
                      adapter?.notifyItemRemoved(removePosition)
                  }
              }
          }
      
          // can be accessed -> list.get(position) or list[position]
          operator fun get(pos: Int): T {
              return list[pos]
          }
      
          // for adapter use
          fun size(): Int {
              return list.size
          }
      
          inline fun forEachIndexed(action: (Int, T) -> Unit) {
              for (index in 0 until size()) {
                  action(index, get(index))
              }
          }
      
          /*
          * UpdateCallback is the main interface that is used to compare the elements.
          *   - sortBy() is used to locate new elements passed to SortedList
          *   - updateBy() is used to update/remove elements
          *
          * Typical example would be Message model class which we want to:
          *   - Sort messages according to their dates
          *   - Update/Remove messages according to their randomIDs or IDs.
          * */
          interface UpdateCallback<T> {
              fun sortBy(i1: T, i2: T): Int
              fun updateBy(oldItem: T, newItem: T): Int
          }
      }
      

      这里解释了用法: https://medium.com/@abduazizkayumov/sortedlist-with-recyclerview-part-2-64c3e9b1b124

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-07-21
        • 1970-01-01
        • 2023-03-20
        • 2011-06-06
        • 1970-01-01
        • 1970-01-01
        • 2012-01-14
        • 2010-10-07
        相关资源
        最近更新 更多