RecyclerView 适配器 notifyDataSetChanged 停止花哨的动画

Posted

技术标签:

【中文标题】RecyclerView 适配器 notifyDataSetChanged 停止花哨的动画【英文标题】:RecyclerView Adapter notifyDataSetChanged stops fancy animation 【发布时间】:2015-02-02 17:35:44 【问题描述】:

我正在构建一个基于 RecyclerView 的组件,允许用户通过拖放重新排序项目。 一旦我在 DragListener 端,我需要它在适配器中的位置才能执行正确的移动,但我只能访问视图。 所以这就是我在适配器视图绑定中所做的:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) 
    Track track = mArray.get(position);
    viewHolder.itemView.setTag(R.string.TAG_ITEM_POSITION, position);

你觉得它正确吗? 因为如果我像这样移动一个项目:

public void move(int from, int to)
    Track track = mArray.remove(from);
    mArray.add(to, track);
    notifyItemMoved(from, to);

那么位置标记不再正确,如果我 notifyDataSetChanged(),我会失去花哨的动画。 有什么建议吗?

【问题讨论】:

【参考方案1】:

有一种方法可以只使用notifyDataSetChanged() 来保存精美的动画

    您需要创建自己的GridLayoutManager,并覆盖supportsPredictiveItemAnimations() 方法返回true

    您需要mAdapter.setHasStableIds(true)

    我觉得棘手的部分是您需要覆盖适配器的 getItemId() 方法。它应该返回真正唯一的值,而不是position 的直接函数。类似mItems.get(position).hashCode()

在我的情况下工作得非常好 - 仅使用 notifyDataSetChanged() 来添加、删除和移动项目的漂亮动画

【讨论】:

你救了我很多痛苦,tyvm。顺便说一句,这应该被接受,而不是上面的答案。 我忘了说,如果您使用 DB 来为适配器提供数据,那么第 3 部分的主键之类的东西将解决您的问题。 嗨,我不明白你需要什么使用“mItems.get(position).hashCode()”返回 long 我需要返回一个对象 ..,你能告诉我吗?以及如何返回对象?谢谢 这是为您的 getItemId (int position) 方法返回 long。 No 3 让我的应用开心了!! :) 它无法控制自己的情绪..【参考方案2】:

不,这是错误的。首先,您不能引用该方法返回后传递给 onBindViewHolder 的位置。 RecyclerView 不会在其位置发生变化时重新绑定视图(由于项目移动等)。

相反,您可以使用ViewHolder#getPosition(),它将返回更新后的位置。

如果你解决了这个问题,你的移动代码应该可以工作并提供漂亮的动画。

调用notifyDataSetChanged 将阻止预测动画,因此请尽可能避免。详情请见documentation。

编辑(来自评论):从外部获取位置,从 recyclerview 获取子视图持有者,然后从 vh 获取位置。详见 RecyclerView api

【讨论】:

好的,谢谢您的回答 =) 但是现在从 adpater 外部检索位置的最佳方法是什么(例如我的 dragListener)? 从 recyclerview 获取子视图持有者,然后从 vh 获取位置。详见 RecyclerView api getPosition() 已被弃用,因为它太模棱两可了。在这种情况下,您需要使用 getLayoutPosition() 但是mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); 很烂。我看到项目被移动了Collections.swap(....,...) 但是当我点击它们时它们是旧的(所以只有项目的标题被改变了)【参考方案3】:

1) 您将使用notifyItemInserted(position);notifyItemRemoved(position); 而不是notifyDataSetChanged() 来制作动画。 2)您可以手动解决您的问题 - 使用

public void move(int from, int to)
    Track track = mArray.remove(from);
    mArray.add(to, track);
    notifyItemMoved(from, to);
    ViewHolder fromHolder = (ViewHolder) mRecyclerView.findViewHolderForPosition(from);
    ViewHolder toHolder = (ViewHolder) mRecyclerView.findViewHolderForPosition(to);
    Tag fromTag = fromHolder.itemView.getTag();
    fromHolder.itemView.setTag(toHolder.itemView.getTag()); 
    toHolder.itemView.setTag(fromTag);


【讨论】:

1. notifyItemChanged(位置)【参考方案4】:

您应该将您的方法移至OnCreateViewHolder,然后notifyItemRemoved(index) 才能正常工作。

【讨论】:

【参考方案5】:

我可以通过将其添加到列表项的外部元素来维持触摸动画

<View
    android:foreground="?android:attr/selectableItemBackground"
...>

【讨论】:

【参考方案6】:

我使用 'notifyItemChanged(int position);' 修复了它而不是 'notifyDataSetChanged();'

我的适配器完美显示精美的动画,没有任何延迟

编辑:我从 onBindViewHolder 的位置得到位置。

【讨论】:

【参考方案7】:

正如上面其他人所说,您可以在适配器上使用 notifyDataSetChanged 时制作动画,尽管您需要专门使用稳定的 ID。如果您的项目 ID 是字符串,您可以为您拥有的每个字符串 ID 生成一个长 ID,并将它们保存在地图中。例如:

class StringToLongIdMap 
    private var stringToLongMap = HashMap<String, Long>()
    private var longId: Long = 0

    fun getLongId(stringId: String): Long 
        if (!stringToLongMap.containsKey(stringId)) 
            stringToLongMap[stringId] = longId++
        
        return stringToLongMap[stringId] ?: -1
    

然后在你的适配器中:

private var stringToLongIdMap = StringToLongIdMap()

override fun getItemId(position: Int): Long 
    val item = items[position]
    return stringToLongIdMap.getLongId(item.id)

另一个需要考虑的有用的事情是,如果您使用 kotlin 数据类作为适配器中的项目,并且您没有 id,则可以使用数据类本身的 hashCode 作为稳定 id(如果您确定项目属性组合在您的数据集中是唯一的):

override fun getItemId(position: Int): Long = items[position].hashCode().toLong()

【讨论】:

以上是关于RecyclerView 适配器 notifyDataSetChanged 停止花哨的动画的主要内容,如果未能解决你的问题,请参考以下文章

Fragment,RecyclerView:没有附加适配器;跳过布局

片段中的recyclerview的“RecyclerView:没有附加适配器;跳过布局”[重复]

如何更新 RecyclerView 适配器数据

RecyclerView 适配器中的 onBackPressed()

当调用 RecyclerView 适配器的“notifyDataSetChanged()”时,RecyclerView 抛出“java.lang.Throwable: addInArray”

RecyclerView:没有附加适配器;跳过布局[重复]