【问题标题】:RecyclerView Adapter notifyDataSetChanged stops fancy animationRecyclerView 适配器 notifyDataSetChanged 停止花哨的动画
【发布时间】: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(),我会失去花哨的动画。 有什么建议吗?

【问题讨论】:

    标签: android adapter android-recyclerview


    【解决方案1】:

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

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

    2. 您需要mAdapter.setHasStableIds(true)

    3. 我觉得棘手的部分是您需要覆盖适配器的 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()
          

          【讨论】:

            猜你喜欢
            • 2018-11-23
            • 1970-01-01
            • 2015-02-27
            • 1970-01-01
            • 1970-01-01
            • 2017-03-19
            • 2017-04-12
            • 2019-01-15
            • 1970-01-01
            相关资源
            最近更新 更多