Android, как сортировать список RecyclerView при использовании AsyncListDiffer?

У меня есть RecyclerView, который показывает список CardViews. Недавно я переключил проект с использования адаптера RecyclerView на использование адаптера AsyncListDiffer, чтобы воспользоваться преимуществами обновлений адаптера в фоновом потоке. Я преобразовал все предыдущие методы CRUD и фильтрации для списка, но не могу заставить работать метод сортировки.

У меня есть разные типы или категории карточек, и я хотел бы отсортировать по типам/категориям. Я клонирую mCards существующего списка, поэтому DiffUtil «за кулисами» увидит его как другой список по сравнению с существующим списком, который я хотел отсортировать. И затем я использую submitList() AsynListDiffer.

Список не сортируется. Что мне здесь не хватает?

Основная деятельность:

      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

Асинклистдифферадаптер:

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

3 ответа

Вместо использования notifyDataSetChanged() мы можем использовать notifyItemMoved(). Это решение дает нам красивую анимацию сортировки. Я установил порядок сортировки в адаптере. Нам нужен 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);
}

а затем вызовите из карт действийAdapter.sortByType();

Я думаю, что проблема в методе ниже

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

потому что здесь сначала вам нужно очистить старый массив "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();

Я клонирую mCards существующего списка, поэтому DiffUtil «за кулисами» увидит его как другой список.

обнаружит изменения вашей реализацией DiffUtil.ItemCallback<Card>, поэтому, когда вы клонируете карту, вы просто создаете новый ее экземпляр с тем же самым, поэтому видите его как один и тот же элемент, потому что вы проверяете, одинаковы ли элементы на основе их 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изменит порядок только один раз.

Другие вопросы по тегам