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
изменит порядок только один раз.