Android Kotlin RecyclerView动画异常问题:选中其他项时第6项及以下重复触发动画
我仔细看了你的代码,问题主要出在这几个核心点上:
全局动画标志+
notifyDataSetChanged()的滥用
你用了全局变量activateAnimation控制动画,而且每次调用toggleSelection都会触发notifyDataSetChanged()。这会让RecyclerView所有可见(包括复用池里刚拿出来的)ViewHolder都重新执行onBindViewHolder,只要selectedItems不为空,这些项都会无条件触发右移动画——这就是第6项及以下(通常是被复用的ViewHolder)重复触发动画的根源。toggleIcon里的错误状态重置
在toggleIcon方法里你写了if(selectedIndex == position) selectedIndex = -1,这会在ViewHolder绑定的时候直接清空选中索引,导致选中状态管理混乱,进一步加剧动画的异常触发。动画触发时机错误
你把动画触发放在onBindViewHolder的toggleIcon里,而ViewHolder是会被复用的,当滚动时,滑出屏幕的ViewHolder会被拿来展示新的项,这时候如果activateAnimation还是true,就会错误地给新绑定的项执行动画。
我们需要针对性解决这些问题,确保只有被操作的项才触发动画,同时避免全局刷新:
1. 移除全局动画标志,改为单位置控制
不需要activateAnimation这个全局变量,而是在选中/取消选中特定位置时,直接给对应的ViewHolder触发动画。
2. 替换notifyDataSetChanged()为精准的notifyItemChanged()
全局刷新会导致所有项重绑定,我们只需要刷新被操作的位置,以及之前选中的位置(如果是单选逻辑的话)。
3. 修正状态管理逻辑
把动画触发从onBindViewHolder移到toggleSelection中,确保只有操作的项才会执行动画,同时在绑定阶段直接设置UI状态,避免复用残留。
修复后的代码
ListAdapter.kt 修正版
class ListItemAdapter(private val values: List<PlaceholderContent.PlaceholderItem> ) : RecyclerView.Adapter<ListItemAdapter.ItemViewHolder>() { private lateinit var itemClick: OnItemClick private var selectedIndex: Int = -1 private var selectedItems: SparseBooleanArray = SparseBooleanArray() private var isActive: Boolean = false fun setItemClick(itemClick: OnItemClick) { this.itemClick = itemClick } override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): ListItemAdapter.ItemViewHolder { val view = LayoutInflater.from(parent.context).inflate( R.layout.fragment_item, parent, false ) return ItemViewHolder(view) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { val item = values[position] holder.itemView.apply { findViewById<TextView>(R.id.item_number).text = item.id findViewById<TextView>(R.id.content).text = item.content } holder.itemView.findViewById<CardView>(R.id.list_item).setOnClickListener { itemClick.onItemClick(item, position) } holder.itemView.findViewById<CardView>(R.id.list_item).setOnLongClickListener { itemClick.onLongPress(item, position) true } // 只负责更新UI状态,不处理动画 updateItemUI(holder, position) } override fun getItemCount(): Int = values.size private fun itemTransition(holder: ItemViewHolder){ val animator = ObjectAnimator.ofFloat(holder.itemView.findViewById(R.id.list_item), View.TRANSLATION_X, 150f) animator.start() } private fun itemTransitionBack(holder: ItemViewHolder){ val animator = ObjectAnimator.ofFloat(holder.itemView.findViewById(R.id.list_item), View.TRANSLATION_X, 0f) animator.start() } private fun updateItemUI(holder: ItemViewHolder, position: Int){ val checkBox = holder.itemView.findViewById<RadioButton>(R.id.is_selected) val isSelected = selectedItems.get(position, false) // 根据激活状态和选中状态控制单选按钮显示 checkBox.isGone = !isActive && !isSelected checkBox.isChecked = isSelected // 直接设置平移位置,避免ViewHolder复用导致状态残留 val cardView = holder.itemView.findViewById<CardView>(R.id.list_item) cardView.translationX = if (isSelected || isActive) 150f else 0f } fun selectedItemCount() = selectedItems.size() fun toggleSelection(position: Int){ val wasSelected = selectedItems.get(position, false) if (wasSelected){ selectedItems.delete(position) // 取消选中时,获取对应ViewHolder执行回移动画 findViewHolderForAdapterPosition(position)?.let { itemTransitionBack(it) } } else { selectedItems.put(position, true) // 选中时,获取对应ViewHolder执行右移动画 findViewHolderForAdapterPosition(position)?.let { itemTransition(it) } } // 更新激活状态 isActive = selectedItems.isNotEmpty() // 只刷新当前操作的位置,避免全局刷新 notifyItemChanged(position) // 如果是单选逻辑(代码里有selectedIndex,推测可能需要),可以添加以下逻辑: // if (selectedIndex != -1 && selectedIndex != position) { // selectedItems.delete(selectedIndex) // notifyItemChanged(selectedIndex) // } // selectedIndex = position } fun clearSelection(){ val positions = mutableListOf<Int>() for (i in 0 until selectedItems.size()) { positions.add(selectedItems.keyAt(i)) } selectedItems.clear() isActive = false // 刷新所有之前选中的位置 positions.forEach { notifyItemChanged(it) } } interface OnItemClick { fun onItemClick(item: PlaceholderContent.PlaceholderItem, position: Int) fun onLongPress(item: PlaceholderContent.PlaceholderItem, position: Int) } inner class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){} }
关键修改点说明:
- 移除了
activateAnimation全局变量,改为在toggleSelection中通过findViewHolderForAdapterPosition获取目标ViewHolder,针对性触发动画。 - 把
toggleIcon改为updateItemUI,只负责更新UI状态(单选按钮显示/隐藏、选中状态,以及卡片平移位置),避免绑定阶段触发动画。 - 替换
notifyDataSetChanged()为notifyItemChanged(position),只刷新被操作的项,减少不必要的重绑定。 - 在
updateItemUI中直接设置translationX,避免ViewHolder复用导致的状态残留。
额外说明
如果你的需求是单选(代码里的selectedIndex暗示了这一点),可以取消toggleSelection中注释的代码,确保每次只能选中一项,同时刷新之前选中的项。如果是多选,保持现有逻辑即可。
内容的提问来源于stack exchange,提问作者pepperlove

