Android RecyclerView适配器notifyDataSetChanged停止高级动画

Android RecyclerView适配器notifyDataSetChanged停止高级动画,android,adapter,android-recyclerview,Android,Adapter,Android Recyclerview,我正在基于RecyclerView构建一个组件,允许用户通过拖放来重新排序项目。 一旦我在DragListener侧,我需要它在适配器中的位置,以便执行正确的移动,但我只能访问视图。 下面是我在适配器视图绑定中所做的工作: @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { Track track = mArray.get(position); viewHolder.itemV

我正在基于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);
}
然后,位置标记不再正确,如果我通知DataSetChanged(),我将丢失精美的动画。
有什么建议吗?

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

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

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

调用notifyDataSetChanged将阻止预测性动画,因此请尽可能避免它。有关详细信息,请参阅


编辑(从注释):要从外部获取位置,请从recyclerview获取子视图持有者,然后从vh获取位置。有关详细信息,请参见RecyclerView api

有一种方法可以仅使用
notifyDataSetChanged()

  • 您需要创建自己的
    GridLayoutManager
    ,使用重写的
    supportsPredictiveItemAnimations()
    方法返回
    true

  • 您需要
    mAdapter.setHasStableIds(true)

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

  • 在我的例子中效果非常好-仅使用
    notifyDataSetChanged()
    1)添加、删除和移动项目的漂亮动画,您将使用
    notifyItemInserted(position)
    notifyItemRemoved(位置)而不是动画的
    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);
    
    }
    

    您应该将方法移动到
    OnCreateViewHolder
    ,然后
    notifyItemRemoved(index)
    正常工作。

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

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

    我使用'notifyItemChanged(int位置);'而不是“notifyDataSetChanged();”

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


    编辑:我从onBindViewHolder的位置获得了位置。

    如上所述,您可以在适配器上使用
    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
        }
    }
    

    另一个有用的考虑事项,如果您使用KOTLIN数据类作为适配器中的项,并且没有ID,则可以使用数据类本身的哈希代码作为稳定的ID(如果您确信项目属性组合在数据集中是唯一的):


    好的,谢谢你的回答=)但是现在从Adpeter外部(例如我的dragListener)检索位置的最佳方法是什么?从recyclerview获取子视图支架,然后从vh获取位置。详细信息请参见RecyclerView api GetPosition()已被弃用,因为它太不明确。在这种情况下,您需要使用getLayoutPosition(),但
    mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition())糟透了。我看到物品被移动了
    集合。交换(………)
    但是当我点击它们时,它们是旧的(所以只有物品的标题被更改了)。你帮我省了很多麻烦,tyvm。顺便说一句,这应该是可以接受的答案,而不是上面的答案。我忘了说,如果您使用DB为适配器提供类似于第3部分主键的内容将解决您的问题。嗨,我不明白您需要什么,请使用“mItems.get(position).hashCode()”返回long我需要返回一个对象..,您能告诉我吗?如何返回对象?thxIt是为你的返回long的方法设计的。no 3是让我的应用程序开心的东西!!:)它无法控制自己的情绪。更改项目(位置)
    private var stringToLongIdMap = StringToLongIdMap()
    
    override fun getItemId(position: Int): Long {
        val item = items[position]
        return stringToLongIdMap.getLongId(item.id)
    }
    
    override fun getItemId(position: Int): Long = items[position].hashCode().toLong()