【问题标题】:Android how to sort RecyclerView List when using AsyncListDiffer?Android使用AsyncListDiffer时如何对RecyclerView List进行排序?
【发布时间】:2022-01-15 23:53:54
【问题描述】:

我有一个显示 CardView 列表的 RecyclerView。我最近将项目从使用 RecyclerView 适配器切换到使用 AsyncListDiffer 适配器,以利用后台线程上的适配器更新。我已经转换了列表的所有以前的 CRUD 和过滤方法,但无法使排序方法正常工作。

我有不同类型或类别的 CardView,我想按类型/类别进行排序。我克隆了现有的 mCards 列表,因此“幕后”DiffUtil 会将其视为与我想要排序的现有列表不同的列表。然后我使用 AsynListDiffer 的 submitList()。

列表未排序。我在这里错过了什么?

主活动:

private static List<Card> mCards = null;

...
mCardViewModel = new ViewModelProvider(this).get(CardViewModel.class);
mCardViewModel.getAllCards().observe(this,(cards -> {

    mCards = cards;
    cardsAdapter.submitList(mCards);
})); 
mRecyclerView.setAdapter(cardsAdapter);

A click on a "Sort" TextView runs the following code:

ArrayList<Card> sortItems = new ArrayList<>();
for (Card card : mCards) {
    sortItems.add(card.clone());
}
Collections.sort(sortItems, new Comparator<Card>() {
    @Override
    public int compare(Card cardFirst, Card cardSecond) {
        return cardFirst.getType().compareTo(cardSecond.getType());
    }
});
cardsAdapter.submitList(sortItems);
// mRecyclerView.setAdapter(cardsAdapter);  // Adding this did not help

AsyncListDifferAdapter:

public AsyncListDifferAdapter(Context context) {

    this.mListItems = new AsyncListDiffer<>(this, DIFF_CALLBACK);
    this.mContext = context;
    this.mInflater = LayoutInflater.from(mContext);
}

public void submitList(List<Quickcard> list) {

    if (list != null) {
        mListItems.submitList(list);
    }
}

public static final DiffUtil.ItemCallback<Card> DIFF_CALLBACK
        = new DiffUtil.ItemCallback<Card>() {

    @Override
    public boolean areItemsTheSame(@NonNull Card oldItem, @NonNull Card newItem) {

        // User properties may have changed if reloaded from the DB, but ID is fixed
        return oldItem.getId() == newItem.getId();
    }
    @Override
    public boolean areContentsTheSame(@NonNull Card oldItem, @NonNull Card newItem) {
        return oldItem.equals(newItem);
    }

    @Nullable
    @Override
    public Object getChangePayload(@NonNull Card oldItem, @NonNull Card newItem) {
        return super.getChangePayload(oldItem, newItem);
    }
};

型号:

@Entity(tableName = "cards")
public class Card implements Parcelable, Cloneable {
// Parcelable code not shown for brevity
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "cardId")
public int id;
@ColumnInfo(name = "cardType")
private String type;

@Ignore
public Card(int id, String type) {
    this.id = id;
    this.type = type;
}

public int getId() {
    return this.id;
}
public String getType() {
    return this.type;
}

@Override
public boolean equals(Object obj) {

    if (obj == this)
        return true;

    else if (obj instanceof Card) {

        Card card = (Card) obj;

        return id == card.getId() &&
            type.equals(card.getType());
    } else {
        return false;
    }
}  

@NonNull
@Override
public Card clone() {
    Card clone;
    try {
        clone = (Card) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
    }
    return clone;
}  

【问题讨论】:

  • 为什么要在这里克隆对象?有什么特别的原因吗?
  • 我克隆了现有的 mCards 列表,因此“幕后”DiffUtil 会将其视为不同的列表

标签: android sorting android-recyclerview android-asynclistdiffer


【解决方案1】:

我们可以使用 notifyItemMoved(),而不是使用 notifyDataSetChanged()。该解决方案为我们提供了一个很好的排序动画。我将排序顺序放在适配器中。我们需要一个包含当前显示元素的 displayOrderList,因为 mDiffer.getCurrentList() 不会在 notifyItemMoved() 之后更改元素的顺序。我们首先将第一个排序的元素移动到第一位,第二个排序的元素移动到第二位,......所以在适配器内部放置以下内容:

public void sortByType()
{
    List<Card> sortedList = new ArrayList<>(mDiffer.getCurrentList());
    sortedList.sort(Comparator.comparing(Card::getType));
    List<Card> displayOrderList = new ArrayList<>(mDiffer.getCurrentList());
    for (int i = 0; i < sortedList.size(); ++i)
    {
        int toPos = sortedList.indexOf(displayOrderList.get(i));
        notifyItemMoved(i, toPos);
        listMoveTo(displayOrderList, i, toPos);
    }
}

private void listMoveTo(List<Card> list, int fromPos, int toPos)
{
    Card fromValue = list.get(fromPos);
    int delta = fromPos < toPos ? 1 : -1;
    for (int i = fromPos; i != toPos; i += delta) {
        list.set(i, list.get(i + delta));
    }
    list.set(toPos, fromValue);
}

然后从活动中调用cardsAdapter.sortByType();

【讨论】:

  • 我去度假了,但我一回来就会尝试。根据完整性授予赏金并接受答案。
  • @AJW 谢谢。节日快乐
  • 代码“sortedList.sort(Comparator.comparing(Quickcard::getType));”需要 Android OS API 级别 24。我可以为 API
  • 让java根据属性排序看stackoverflow.com/questions/2784514/…
【解决方案2】:

我认为问题出在以下方法

public void submitList(List<Quickcard> list) {
    
        if (list != null) {
            mListItems.submitList(list);
        }
}

因为这里首先你需要使用清除旧的arraylist "mListItems"

mListItems.clear();
//then add new data 
 

if (list != null) {
    mListItems.addAll(list);
}
 
//now notify adapter
notifyDataSetChanged();

或者您也可以在排序后直接通知适配器。 首先设置适配器并在适配器的构造函数中传递您的主列表

Collections.sort(sortItems, new Comparator<Card>() {
@Override
public int compare(Card cardFirst, Card cardSecond) {
    return cardFirst.getType().compareTo(cardSecond.getType());
}

});

//now direct notify adpter
your_adapter_object.notifyDataSetChanged();

【讨论】:

  • 我会尝试清除适配器列表然后addAll()。但是使用 AsyncListDiffer 更新更新的原因是因为它在更新时效率更高,并且发生在后台线程上,而不是依赖于繁重的 notifyDataSetChanged() 方法。
  • notifyDataSetChanged() 也发生在主线程上。
  • 但没有 notifyDataSetChanged() 适配器如何知道数据已更新。如果在 submitList() 中更新了数据,那么列表也更新了,但是适配器现在将如何?如果没有通知,我认为它不会更新。
【解决方案3】:

我克隆了现有的 mCard 列表,因此“幕后”DiffUtil 会将其视为不同的列表

DiffUtil 将通过您的DiffUtil.ItemCallback&lt;Card&gt; 实现检测更改,因此当您克隆卡时,您只需使用相同的ID 创建它的新实例,因此DiffUtil 将其视为同一个项目,因为您正在根据 ID 检查项目是否相同:

 @Override
public boolean areItemsTheSame(@NonNull Card oldItem, @NonNull Card newItem) {

    // User properties may have changed if reloaded from the DB, but ID is fixed
    return oldItem.getId() == newItem.getId();
}

但是由于克隆对象的引用与原始项目不同,DiffUitl 会检测到项目内容的变化,原因是:

@Override
public boolean areContentsTheSame(@NonNull Card oldItem, @NonNull Card newItem) {
    return oldItem.equals(newItem);
}

所以DiffUtil 将调用adapter.notifyItemChanged(updateIndex) 并更新该索引中的viewHolder,但不会更改recyclerView 中项目的顺序。

您的排序代码中似乎还有另一个问题。你说

我想按类型/类别排序

但您每次单击textView 时都会按字母顺序对列表进行排序。此代码每次运行时都会为您提供相同的结果,并且不会更改 recyclerView 的顺序,假设原始列表是第一手排序的。如果原始列表未排序,单击textView 将只更改一次顺序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-01
    • 2015-12-10
    • 1970-01-01
    相关资源
    最近更新 更多