Android 使用AsyncListDiffer时如何对RecyclerView List进行排序?
Android how to sort RecyclerView List when using AsyncListDiffer?
我有一个显示 CardView 列表的 RecyclerView。我最近将项目从使用 RecyclerView 适配器切换到使用 AsyncListDiffer 适配器以利用后台线程上的适配器更新。我已经为列表转换了所有以前的 CRUD 和筛选方法,但无法使排序方法正常工作。
我有不同类型或类别的 CardView,我想按 types/categories 排序。我克隆了现有列表 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;
}
我认为问题出在下面的方法中
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();
或者也可以排序后直接通知adapter。
首先设置适配器并在适配器的构造函数中传递您的主列表
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();
I clone the existing list mCards so the "behind the scenes" DiffUtil will see it as a different list
DiffUtil
将通过您实施 DiffUtil.ItemCallback<Card>
来检测更改,因此当您克隆一张卡片时,您只需使用相同的 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
中项目的顺序。
您的排序代码似乎还有问题。你说
I would like to sort by the types/categories
但是您每次单击 textView
时都会按字母顺序对列表进行排序。这段代码每次 运行 都会给你相同的结果,并且不会更改 recyclerView
的顺序,假设原始列表是第一手排序的。如果原始列表未排序,单击 textView
将仅更改一次顺序。
我们可以使用 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);
}
然后从 activity cardsAdapter.sortByType();
调用
我有一个显示 CardView 列表的 RecyclerView。我最近将项目从使用 RecyclerView 适配器切换到使用 AsyncListDiffer 适配器以利用后台线程上的适配器更新。我已经为列表转换了所有以前的 CRUD 和筛选方法,但无法使排序方法正常工作。
我有不同类型或类别的 CardView,我想按 types/categories 排序。我克隆了现有列表 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;
}
我认为问题出在下面的方法中
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();
或者也可以排序后直接通知adapter。 首先设置适配器并在适配器的构造函数中传递您的主列表
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();
I clone the existing list mCards so the "behind the scenes" DiffUtil will see it as a different list
DiffUtil
将通过您实施 DiffUtil.ItemCallback<Card>
来检测更改,因此当您克隆一张卡片时,您只需使用相同的 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
中项目的顺序。
您的排序代码似乎还有问题。你说
I would like to sort by the types/categories
但是您每次单击 textView
时都会按字母顺序对列表进行排序。这段代码每次 运行 都会给你相同的结果,并且不会更改 recyclerView
的顺序,假设原始列表是第一手排序的。如果原始列表未排序,单击 textView
将仅更改一次顺序。
我们可以使用 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);
}
然后从 activity cardsAdapter.sortByType();
调用