为啥在recyclerview android中滚动后突出显示的项目丢失
Posted
技术标签:
【中文标题】为啥在recyclerview android中滚动后突出显示的项目丢失【英文标题】:Why are highlighted items lost after scrolling in recylerview android为什么在recyclerview android中滚动后突出显示的项目丢失 【发布时间】:2021-12-15 01:13:25 【问题描述】:我想在适配器类中的操作模式处于活动状态时突出显示该项目。我可以这样做,但滚动后突出显示状态消失了。我尝试了各种解决方案,但我不明白为什么会这样?
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder>
class MyViewHolder extends RecyclerView.ViewHolder
public void bind(Items viewHolder_item)
itemView.setOnLongClickListener(new View.OnLongClickListener()
@Override
public boolean onLongClick(View v)
isSelectMode = true;
if (viewHolder_item.getIsSelect())
itemView.setBackgroundColor(Color.TRANSPARENT);
item.get(position).setSelect(false);
selectedList.remove(item.get(position));
else
itemView.setBackgroundColor(Color.GRAY);
item.get(position).setSelect(true);
selectedList.add(item.get(position));
if (selectList.size() == 0)
isSelectMode = false;
return true;
);
itemView.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
if (isSelectMode)
if (viewHolder_item.getIsSelect())
itemView.setBackgroundColor(Color.TRANSPARENT);
item.get(position).setSelect(false);
selectedList.remove(item.get(position));
else
itemView.setBackgroundColor(Color.GRAY);
item.get(position).setSelect(true);
selectedList.add(item.get(position));
if (selectList.size() == 0)
isSelectMode = false;
);
无论实施哪种解决方案,结果总是相同的。滚动后突出显示的颜色消失了。任何帮助将不胜感激。
【问题讨论】:
在您的代码中,您可以同时使用 backgroundColor 和 backgroundResource。尝试只使用一个。同样在 onBindViewHolder 而不是调用 getadapterposition() 您可以简单地使用方法提供的位置 感谢您的回答,但即使进行了这些更改,结果也是一样的。 我在问题上方也进行了编辑,以向您展示实现。 在 onBindViewHolder 方法的第一个 if 语句中添加 Log.d() 以查看 contains 子句是否正常工作,即找出错误是否发生在绑定或设置红色时 我在 if 块中添加了Log.e("TAG", " contains " + selectedList.contains(items.get(position)));
,如上所示。即使您突出显示一个项目并向上滚动,它也总是返回 false。
【参考方案1】:
我建议您采用替代方法来解决您的问题。在您的 Item 对象中添加一个名为 public boolean isSelected=false
的布尔字段,然后在单击项目时使用 items.get(position).isSelected=true
在您的 items
列表中将该字段设置为 true,然后使用此字段来评估每个项目应该处于哪个状态,而不是使用您的selectedList
。你实际上不再需要这个列表了。
所以现在你只需要修改你的 bindViewHolder 如下:
@Override
public void onBindViewHolder(@NonNull MyAdapter.MyViewHolder holder, int position)
Items item=items.get(position);
if (!item.getIsSelect())
holder.itemView.setBackgroundColor(Color.TRANSPARENT);
else
holder.itemView.setBackgroundColor(activity.getResources().getColor(R.color.red));
holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
@Override
public boolean onLongClick(View v)
isSelectMode = true;
if (item.getIsSelect())
holder.itemView.setBackgroundColor(Color.TRANSPARENT);
items.get(position).setSelect(false);
selectedList.remove(items.get(position));
else
holder.itemView.setBackgroundColor(activity.getResources().getColor(R.color.red));
items.get(position).setSelect(true);
selectedList.add(items.get(position));
if (selectList.size() == 0)
isSelectMode = false;
return true;
);
holder.itemView.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
if (isSelectMode)
if (item.getIsSelect())
holder.itemView.setBackgroundColor(Color.TRANSPARENT);
items.get(position).setSelect(false);
selectedList.remove(items.get(position));
else
holder.itemView.setBackgroundColor(activity.getResources().getColor(R.color.red));
items.get(position).setSelect(true);
if (selectList.size() == 0)
isSelectMode = false;
);
顺便说一句。您不需要将 isSelect 添加到 Items 的构造函数中,因为这应该是一个临时会话特定变量
有很多实例会使 contains 无法正常工作,正如您所期望的那样,我们可能必须深入您的 Item 对象才能找到原因这里只是一个链接Why is .contains returning false when I add a duplicate?
【讨论】:
恐怕它给出的结果和以前一样。我一直在尝试各种方法,但都做不到。 这毫无意义。您能否再次在 if 语句中添加日志并记录 isSelect() 变量。同样在您的片段类中,我看不到您创建项目对象的位置,请您证明一下 我已经在片段类中添加了项目创建。 至于这条语句Log.e("TAG","isSelect " + item.IsSelect());
它总是像以前一样返回false。
抱歉,我显然忘了将 setSelect 添加到我的代码中。我的错,现在就做。再试一次,让我知道【参考方案2】:
对现有答案的一些补充:
-
将您的适配器数据视为不可变。不要改变适配器内的数据。适配器需要嗯...将数据从模型适应到视图(Holder)中。它会根据定位跟踪每个数据所属的位置,这几乎就是它需要做的所有事情(除了自然地膨胀视图)。
如果您提供
Thing
的列表,其中isSelected
不是模型的一部分,而是您需要跟踪用户是否选择它的一部分,然后创建一个简单的数据类,如
data class ThingSelection(val thing: Thing, val isSelected: Boolean)
并在您的适配器中使用它,而不仅仅是 List<Thing>
,使用 List<ThingSelection>
。
-
不要做你在
onBindViewHolder
中所做的事情。你应该做的就是
val item = getItem(position)
holder.bind(item)
ViewHolder 的工作是根据您的业务规则设置其属性、背景颜色等。
如果你的“项目”是ThingSelection
,那么 viewHolder 的绑定方法就可以了
if (item.isSelected) ...
也将您需要的任何其他内容传递给此方法,bind(...)
就是您需要的任何内容。
当用户改变item选择时怎么办?
你有一个点击监听器,但是不要改变数据,让你的回调/监听器将发生了什么传递给调用者。
我想在你的 viewHolder 绑定方法中你会做这样的事情:
view.setOnClickListener
externalListenerThatTheAdapterReceivedFromTheOutside.onThingClicked(thing, thing.isSelected)
这个外部听众有责任:
尽其所能(可能直接将其传递给 ViewModel,以便它处理这个新事件),以确保适配器接收新数据(现在 Thing.isSelected 是正确的,因为用户做了什么)。
确保将新的不可变列表提供给适配器,以便适配器可以计算其新数据并进行更新(提示:使用 ListAdapter<T, K>
和 DiffUtil.ItemCallback<T>
,这会让您的生活更轻松。
更新:数据类
data class ItemWithSelection(val item: Item, val isSelected: Boolean)
然后将您的适配器更改为:
class YourAdapter(): ListAdapter<ItemWithSelection, YourViewHolder>(DiffUtilCallback())
// You only need to override these
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): YourViewHolder
val view = LayoutInflater.from(parent.context).inflate(R.layout.your_item_layout, parent, false) // or use ViewBinding, whatever.
return YourViewHolder(view) // you can pass more stuff if you need here, like a listener...
override fun onBindViewHolder(holder: YourViewHolder, position: Int)
val item = getItem(position)
holder.bindData(item, item.isSelected) //Item is of type ItemWithSelection...
// Somewhere in your ViewHolder...
fun bindData(item: ItemWithSelection, isSelected: Boolean)
// do what it needs, e.g.:
if (isSelected) ...
// You also need a DiffUtilCallback...
internal class DiffUtil.ItemCallback<ItemWithSelection>()
internal class DiffUtilCallback : DiffUtil.ItemCallback<ItemWithSelection>()
override fun areItemsTheSame(oldItem: ItemWithSelection, newItem: ItemWithSelection) = oldItem.item.xxx == newItem.item.xxx
override fun areContentsTheSame(oldItem: ItemWithSelection, newItem: ItemWithSelection) = oldItem == newItem
所有这些都连接起来......
在您的ViewModel
或“获取数据的层”(很可能不是片段/活动)中提供List<ItemWithSelection>
...
如何构造这个真的取决于你如何存储选择的内容。
但为了简单起见,假设您有两个列表:
val all: List<Item>
val selected: List<Item>
当需要为 UI 生成列表时,您可以(注意:我假设您使用协程......您可以选择自己的异步/反应式编程风格:
class YourViewModel: ViewModel()
fun getYourListWithSelections()
viewModelScope.launch(Dispatchers.Computation) //for e.g.
val tempSelection = mutableListOf<ItemWithSelection>()
all.mapTo(tempSelection)
ItemWithSelection(item = it, isSelected = selection.contains(it))
// Assuming you have a MutableLiveData somewhere in this viewmodel...
_uiState.postValue(SomeSealedClass.ListAvailable(tempSelection))
这是从Fragment中自然观察到的……
class YourFragment: Fragment()
fun observe()
viewModel.uiState.observe(viewLifecycleOwner, viewState ->
when (viewState)
is SomeSealedClass.ListAvailable -> adapter.submitList(viewState.items.toMutableList())//need to pass a new list or ListAdapter won't refresh the list.
else ... //exercise for the reader ;)
)
这里真正缺少的只是样板文件。
-
ViewHolder(在 View 上设置
click listener
)如何将信息传递回 ViewModel?
为您的适配器提供您选择的侦听器:
interface ItemClickListener
fun onItemClicked(val item: Item, val isSelected: Boolean)
实现它并在您创建它时将其传递给您的适配器:
class YourAdapter(private val listener: ItemClickListener): ListAdapter<ItemWithSelection, YourViewHolder>(DiffUtilCallback())
并将其传递到您的片段中:
class YourFragment: Fragment()
private val listener = object : ItemClickListener
fun onItemClicked(val item: Item, val isSelected: Boolean)
// pass it directly, it's the VM job to deal with this and produce a new list via LiveData.
viewModel.onItemClicked(item, isSelected)
val adapter = YourAdapter(listener)
希望对您有所帮助。
【讨论】:
感谢您的回答。我按照您的建议从适配器中移动了数据,并在上面的代码中也进行了更改,但我对关于简单数据类的第二点感到困惑。我不知道如何创建和使用一个。如果你能用一点代码解释一下,那将是非常有帮助的。以上是关于为啥在recyclerview android中滚动后突出显示的项目丢失的主要内容,如果未能解决你的问题,请参考以下文章
为啥在recyclerview android中滚动后突出显示的项目丢失
为啥 CardView 和 RecyclerView 需要 minSdkVersion L?
为啥 RecyclerView 项目在 GridLayoutManager 中随机移动位置?
为啥 RecyclerView 项目在点击两次后才改变背景颜色?