RecyclerView 动画问题 Android Kotlin
Posted
技术标签:
【中文标题】RecyclerView 动画问题 Android Kotlin【英文标题】:RecyclerView animation issue Android Kotlin 【发布时间】:2021-10-13 00:35:16 【问题描述】:我在 recyclerview 中添加了一个动画来显示下面的过渡。
当我长按一个项目时,它会显示单选按钮并且项目卡会向右移动。问题是,在初始选择后,当我单击或选择其他项目时,item6 和下面的项目再次动画。
谁能解释为什么会发生这种情况以及我该如何解决这个问题。
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
private var activateAnimation: 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)
holder.itemView.apply
findViewById<TextView>(R.id.item_number).text = values[position].id
findViewById<TextView>(R.id.content).text = values[position].content
holder.itemView.findViewById<CardView>(R.id.list_item).setOnClickListener
itemClick.onItemClick(values[position], position)
holder.itemView.findViewById<CardView>(R.id.list_item).setOnLongClickListener
itemClick.onLongPress(values[position], position)
true
toggleIcon(holder, position)
override fun getItemCount(): Int
return 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()
fun toggleIcon(holder: ItemViewHolder, position: Int)
val checkBox = holder.itemView.findViewById<RadioButton>(R.id.is_selected)
if(selectedItems.get(position, false))
checkBox.isGone = false
checkBox.isChecked = true
else
checkBox.isGone = true
checkBox.isChecked = false
if(isActive) checkBox.isGone = false
if(activateAnimation)
itemTransition(holder)
else
itemTransitionBack(holder)
if(selectedIndex == position) selectedIndex = - 1
fun selectedItemCount() = selectedItems.size()
fun toggleSelection(position: Int)
selectedIndex = position
if (selectedItems.get(position, false))
selectedItems.delete(position)
else
selectedItems.put(position, true)
notifyItemChanged(position)
isActive = selectedItems.isNotEmpty()
activateAnimation = selectedItems.isNotEmpty()
notifyDataSetChanged()
fun clearSelection()
selectedItems.clear()
notifyDataSetChanged()
interface OnItemClick
fun onItemClick(item: PlaceholderContent.PlaceholderItem, position: Int)
fun onLongPress(item: PlaceholderContent.PlaceholderItem, position: Int)
inner class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
ItemFragment.kt
adapter = ListItemAdapter(PlaceholderContent.ITEMS)
val recyclerViewList = view.findViewById<RecyclerView>(R.id.list)
recyclerViewList.adapter = adapter
recyclerViewList.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
val myHelper = ItemTouchHelper(myCallback)
myHelper.attachToRecyclerView(recyclerViewList)
adapter.setItemClick(object : ListItemAdapter.OnItemClick
override fun onItemClick(
item: PlaceholderContent.PlaceholderItem,
position: Int
)
if(adapter.selectedItemCount() > 0)
toggleSelection(position)
override fun onLongPress(
item: PlaceholderContent.PlaceholderItem,
position: Int
)
toggleSelection(position)
)
private fun toggleSelection(position: Int)
adapter.toggleSelection(position)
【问题讨论】:
您能解释一下为什么在toggleSelection(position: Int)
方法中同时使用notifyItemChanged
和notifyDataSetChanged
吗?他们每次都在调用更新单个项目的状态。你检查过删除那部分吗?告诉我
使用 notifyDataSetChanged() 的原因是通知所有项目它们应该显示复选框以供选择。然后在整个 recyclerview 项目中所有复选框都可见并且用户按下随机项目之后。我需要通知该项目以显示复选框已选中,为此我使用 notifyItemChanged。
但这两个语句强制每个项目都更新,但您的视图持有者当前正常持有 5 个项目,您应该只更新正在更新其视图的项目位置。调用 notifyDataSetChanged 成为此解决方案的昂贵语句。如果你在 GitHub 上有这个项目,请分享 GitHub 公开链接,以便我正确查看。
【参考方案1】:
您可以覆盖getItemViewType(int position)
,这将返回始终唯一的视图ID。
@Override
public int getItemViewType(int position)
return position;
或
@Override
public int getItemViewType(final int position)
return R.layout.fragment_item;
由于 android 系统将每个布局的静态引用存储为“R”(资源)类中的 Integer,我们可以简单地返回布局资源 id 以在 onCreateViewHolder()
方法中使用。
【讨论】:
【参考方案2】:您在adapter.toggleSelection(position)
中调用notifyDataSetChanged()
,无论此位置是否已更新,这都会重新绑定所有可见视图(并再次运行动画)。
更新
如 cmets 中所述,第 6 项动画的原因很可能是由于 RecyclerView 保留的默认 ViewPool(5 项)。第 6 个视图不是其中的一部分,因此它会重新绑定、重新显示,并且...重新设置动画。
我会做的是:
-
我可以摆脱
notifyDataSetChanged()
吗?你为什么这么叫?
我可以利用RecyclerView-Selection,因为它是一个谷歌图书馆,他们建议我们使用什么?这样做的好处是更少的“自定义”代码。
除此之外,您可以尝试按照 cmets 中 Pawel 的建议增加 RecycledViewPool。请记住,这可能会被视为代码异味,因为不同的分辨率、密度、屏幕尺寸等可能会影响它在运行时的行为;这会很不稳定并且容易失败,但根据您的特定用例,可能会让您暂时摆脱它。
【讨论】:
你能解释一下为什么动画在最初选择后只在item6及以下运行吗? 我不能不运行它和调试,但我的猜测是适配器认为/认为那些 ViewHolders 在调用 notifyDataSetChanged 后已经处于正确状态。请记住,这在某种程度上都是异步的。 您能否提出任何可能的解决方案来解决这个问题? 这是在没有项目在前面/运行/尝试 20000 件事情的情况下很难弄清楚的问题之一。我的第一个建议是:你能摆脱notifyDataSetChanged
吗?来自 Google 的 the RecyclerView-Selection 课程能否帮助您使用“较少定制”的解决方案? (只是折腾想法)。
RecyclerView.RecycledViewPool 默认大小为 5,您可以通过增加该数量来进行粗略的修复。您还可以尝试设置适配器的 setHasStableIds 并覆盖 getItemId。【参考方案3】:
这是一种 hack,但你考虑过使用吗?
holder.setIsRecyclable(false);
如果列表不会很大并且如果它解决了问题,则可以用作快速解决方案。
【讨论】:
这个用不了,列表真的很大。【参考方案4】:代替 position 尝试使用 holder.getAdapterPosition() 与您在适配器的绑定方法中执行的所有点击操作相同,
holder.itemView.findViewById<CardView>(R.id.list_item).setOnClickListener
itemClick.onItemClick(values[position], holder.getAdapterPosition())
holder.itemView.findViewById<CardView>(R.id.list_item).setOnLongClickListener
itemClick.onLongPress(values[position], holder.getAdapterPosition())
true
toggleIcon(holder, holder.getAdapterPosition())
【讨论】:
这似乎不起作用。您能分享一下您对这种方法的看法吗? 请浏览本博客内容proandroiddev.com/…以上是关于RecyclerView 动画问题 Android Kotlin的主要内容,如果未能解决你的问题,请参考以下文章
Android RecyclerView - 触摸时动画项目提升
Android Kotlin:如何从 recyclerview 中成功删除项目、调用 notifyItemRangeChanged() 和动画
Android番外篇 RecyclerView 移除飞行效果动画
Android RecyclerView的notify方法和动画的刷新详解