【问题标题】:RecyclerView animation issue Android KotlinRecyclerView 动画问题 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 公开链接,以便我正确查看。

标签: android kotlin android-recyclerview android-animation


【解决方案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 个视图不是其中的一部分,因此它会重新绑定、重新显示,并且...重新设置动画。

    我会做的是:

    1. 我可以摆脱notifyDataSetChanged() 吗?你为什么这么叫?
    2. 我可以利用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/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-25
    • 2018-10-26
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多