这是因为回收器视图概念重用视图而不是每次滚动时都创建新视图。
如果您想在回收站视图中显示 100 个项目,并且其中只有 20 个可以显示给用户,则回收站视图仅创建 20 个视图持有者来代表这 20 个项目,只要用户滚动回收站视图仍将只有 20 个视图持有者,但只会切换存储在此视图持有者中的数据,而不是创建新的视图持有者。
现在要处理您的项目选择,有两种方法可以做到这一点。
天真的方式
- 将选择保存在回收视图适配器内的布尔数组中。
- 每当用户滚动时,适配器都会调用 onBindViewHolder 以使用适当的数据更新可见视图。
- 所以当 onBindViewHolder 被调用时,只需使用方法调用中发送的位置根据布尔数组设置单选按钮选择
- 在您对回收站视图的使用结束时,您可以在适配器中创建一个 getter 方法来获取布尔值的选择数组列表并根据它传递数据
public class PhotosGalleryAdapter extends RecyclerView.Adapter<PhotosGalleryViewHolder> {
ArrayList<Your_Data_ClassType> data;
ArrayList<Boolean> dataSelected ;
public PhotosGalleryAdapter(ArrayList<Your_Data_ClassType> data) {
this.data = data;
dataSelected = new ArrayList<>(data.size()) ;
}
...
@Override
public void onBindViewHolder(@NonNull PhotosGalleryViewHolder holder, int position) {
...
RadioButton radioButton = holder.getRadioButton()
radioButton.setChecked(dataSelected.get(position));
radioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
dataSelected.set(holder.getAbsoluteAdapterPosition() , isChecked) ;
}
});
...
}
}
另一种方法是使用选择跟踪器,它应该是在回收器视图中处理选择的正确方法。
这种方式的问题是它需要对代码进行大量编辑并创建新类以作为参数包含在选择跟踪器中,但最终你会发现它值得你花时间在上面。
为了以这种方式开始,您需要执行以下操作:
-
首先,决定什么应该是键(String-Long-Parcelable),以便跟踪器用来区分您的数据,最安全的方法是 String 或 Parcelable,因为我曾经尝试过 Long 并最终得到了很多很多问题(在你的情况下,我假设它是照片的 uri,类型为 string)
-
其次,您需要创建两个新类,一个扩展 ItemDetailsLookup,另一个扩展 ItemKeyProvider,并且应该使用键作为它们的泛型类型(放在 之间的类型)
你的两个类应该是这样的(你可以直接复制它们)
扩展ItemKeyProvider:
public class GalleryItemKeyProvider extends ItemKeyProvider<String>{
PhotosGalleryAdapter adapter ;
/**
* Creates a new provider with the given scope.
*
* @param scope Scope can't be changed at runtime.
*/
public GalleryItemKeyProvider(int scope,PhotosGalleryAdapter m_adapter) {
super(scope);
this.adapter = m_adapter;
}
@Nullable
@Override
public String getKey(int position) {
return adapter.getKey(position);
}
@Override
public int getPosition(@NonNull String key) {
return adapter.getPosition(key);
}
}
扩展 ItemDetailsLookup 的:
public class GalleryDetailsLookup extends ItemDetailsLookup<String> {
private final RecyclerView recView ;
public GalleryDetailsLookup(RecyclerView m_recView){
this.recView = m_recView;
}
@Nullable
@Override
public ItemDetails<String> getItemDetails(@NonNull MotionEvent e) {
View view = recView.findChildViewUnder(e.getX(), e.getY());
if (view != null) {
RecyclerView.ViewHolder holder = recView.getChildViewHolder(view);
if (holder instanceof PhotosGalleryViewHolder) {
return ((PhotosGalleryViewHolder) holder).getItemDetails();
}
}
return null;
}
}
- 第三,您应该在适配器中包含这两个新方法以供上述类使用
public class PhotosGalleryAdapter extends RecyclerView.Adapter<PhotosGalleryViewHolder> {
...
public String getKey(int position) {
return data.get(position).getUri();
}
public int getPosition(String key) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i).getUri() == key) return i;
}
return 0;
}
...
}
- forthly(如果有一个英文单词称为throwly),您应该使用之前创建的所有上述类初始化跟踪器,其余的将由他处理,跟踪器作为参数
- 唯一的选择跟踪器 ID(如果这将是您将使用的唯一选择跟踪器,则将其命名)
- 我们创建的 ItemKeyProvider
- 我们创建的 DetailsLookup
- String-Long-Parcelable Storage 用于存储在其中选择的键(在我们的例子中,它将是 String Storage)
- 一个选择谓词,它负责处理您想要做的选择方式,您希望它能够(仅选择一项 - 无限制的多项选择 - 基于一种奇怪的算法,如仅偶数或仅奇数),在我的情况下,我将使用默认的多项选择,但如果您想使用另一种选择算法来更改它,您应该创建一个扩展 SelectionPredicates 的新类并实现您的选择方式,您也可以只需检查其他默认值可能就是您要查找的内容。
无论如何,这就是初始化的样子(无论是在片段还是活动方法中,您都应该将此代码放在初始化回收器视图的任何位置):
private void initRecycleView() {
...
SelectionTracker<String> tracker = new SelectionTracker.Builder<>("PhotosGallerySelection",
Your_Recycler_View,
new GalleryItemKeyProvider(ItemKeyProvider.SCOPE_MAPPED, photosAdapter),
new GalleryDetailsLookup(Your_Recycler_View),
StorageStrategy.createStringStorage())
.withSelectionPredicate(SelectionPredicates.createSelectAnything())
.build();
...
}
- 我没有找到一种方法让我用数据初始化适配器然后创建跟踪器以使视图持有者知道他们的选择,所以在这种情况下我首先创建了跟踪器然后让适配器知道关于使用 setter 和
notifyDataSetChanged 的数据
我的意思是在创建跟踪器后立即将跟踪器和数据设置到适配器,所以 initRecycleView 应该是这样的
private void initRecycleView() {
...
SelectionTracker<String> tracker = new SelectionTracker.Builder<>("PhotosGallerySelection",
Your_Recycler_View,
new GalleryItemKeyProvider(ItemKeyProvider.SCOPE_MAPPED, photosAdapter),
new GalleryDetailsLookup(Your_Recycler_View),
StorageStrategy.createStringStorage())
.withSelectionPredicate(SelectionPredicates.createSelectAnything())
.build();
photosAdapter.setTracker(tracker);
photosAdapter.setData(data);
photosAdapter.notifyDataSetChanged();
...
}
- 最后但同样重要的是,您应该处理视图持有者应该如何知道它们是否被选中,因此您应该通过在其中创建一个 setter 方法让适配器知道跟踪器及其数据,这就是适配器应该如何最后的样子:
public class PhotosGalleryAdapter extends RecyclerView.Adapter<PhotosGalleryViewHolder> {
ArrayList<Your_Data_Class> data;
private SelectionTracker<String> tracker;
public PhotosGalleryAdapter() {
data = new ArrayList<>();
}
public ArrayList<Your_Data_Class> getData() {
return data;
}
public void setData(ArrayList<Your_Data_Class> m_data) {
this.data = m_data;
}
@Override
public ScheduleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
...
}
@Override
public void onBindViewHolder(@NonNull PhotosGalleryViewHolder holder, int position) {
...
boolean isSelected = tracker.isSelected(data.get(i).getUri());
RadioButton radioButton = holder.getRadioButton;
radioButton.setChecked(isSelected);
}
@Override
public int getItemCount() {
return data.size();
}
public String getKey(int position) {
return data.get(position).getUri();
}
public int getPosition(String key) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i).getUri() == key) return i;
}
return 0;
}
public void setTracker(SelectionTracker<String> m_tracker) {
this.tracker = m_tracker;
}
}
(您可能会注意到,如果您通过构造函数使用其数据初始化适配器,当他询问跟踪器是否选择了某个项目时,它将导致 NullPointerException,因为在初始化适配器时您仍然没有初始化跟踪器)
-
这样你就可以按照谷歌在他们的文档中建议的方式跟踪你的选择(老实说,我不知道为什么它会变得如此复杂)。
-
如果您想在应用程序/片段使用结束时知道所有选定的项目,您应该调用tracker.getSelection(),它将返回一个选择列表供您迭代
-
跟踪器有一个小问题/功能,它不会开始选择第一个项目,直到你长按它,如果你确实想要这个功能,这只发生在你选择的第一个项目中(开始选择长按模式)然后保持原样
如果您不想要它,您可以让跟踪器在开始时选择一个幽灵键(任何对您的数据没有任何意义的唯一字符串键),稍后只需单击任何照片即可启用选择模式
tracker.select("");
这也是在开始时进行默认/旧选择的方法,如果您确实希望跟踪器从选择的少数项目开始,您可以创建一个 for 循环并调用 tracker.select(Key)
注意:如果您使用 Ghost Key 方法,您应该注意当您调用 tracker.getSelection() 时将返回的选择数组也将包含此 Ghost Key。
最后,如果您有兴趣阅读文档中有关选择跟踪器的内容,请关注link
或者如果您知道如何阅读 kotlin,请点击这两个链接
implementing-selection-in-recyclerview
a guide to recyclerview selection
在我想出如何做这一切之前,我被困在选择问题上好几天了,所以我希望你能找到解决办法。