RecyclerView中移除项目时没有动画效果。

62

我第一次使用 RecyclerView。除了删除项目时没有动画效果之外,其他都正常工作。

我没有设置自定义项动画,但根据文档

RecyclerView 默认启用添加和删除项目的动画效果。

因此,删除时的动画应该有效。

我想在删除时有默认的动画效果,但无法实现。

这是我设置 RecyclerView 的方式:

private void setupRecyclerView() {
  mRecyclerView = (RecyclerView) mRootView.findViewById(R.id.recycler_view);
  mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
  View emptyView = mRootView.findViewById(R.id.empty_view);
  mAdapter = new RoutineAdapter(getActivity(), mRoutineItems, emptyView);
  mRecyclerView.setAdapter(mAdapter);
}

这是我的适配器:

private class RoutineAdapter
      extends RecyclerView.Adapter<RoutineAdapter.ViewHolder> {

private final Context mContext;
private List<RoutineItem> mData;
private View mEmptyView;

    public RoutineAdapter(Context context, List<RoutineItem> data, View emptyView) {
      mContext = context;
      mData = data;
      mEmptyView = emptyView;
      setEmptyViewVisibility();
    }

    public void add(RoutineItem routineItem, int position) {
      mData.add(position, routineItem);
      setEmptyViewVisibility();
      notifyItemInserted(position);
    }

    public void remove(int position){
      mData.remove(position);
      setEmptyViewVisibility();
      notifyItemRemoved(position);
    }

    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      final View view = LayoutInflater.from(mContext).inflate(
          R.layout.fragment_routines_list_item, parent, false);
      return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
      final RoutineItem routineItem = getItem(position);
      holder.circle.setBackgroundResource(
          colorNumberToDrawableResource(routineItem.colorNumber));
      holder.initial.setText(routineItem.routineName.substring(0, 1));
      holder.routineName.setText(routineItem.routineName);
      holder.lastTimeDone.setText(routineItem.lastTimeDoneText);
      if (routineItem.isSelected) {
        holder.itemView.setBackgroundColor(
            getResources().getColor(R.color.background_item_selected));
      } else {
        holder.itemView.setBackgroundResource(
            R.drawable.darker_background_on_pressed);
      }
      holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
          mPresenter.onRoutineClicked(routineItem.routineName);
        }
      });
      holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
          mPresenter.onRoutineLongClicked(routineItem.routineName);
          return true;
        }
      });
    }

    @Override
    public int getItemCount() {
      return mData.size();
    }

    public RoutineItem getItem(int position) {
      return mData.get(position);
    }

    private void setEmptyViewVisibility() {
      if (getItemCount() == 0) {
        mEmptyView.setVisibility(View.VISIBLE);
      } else {
        mEmptyView.setVisibility(View.GONE);
      }
    }

    class ViewHolder extends RecyclerView.ViewHolder {
      public final View circle;
      public final TextView initial;
      public final TextView routineName;
      public final TextView lastTimeDone;

      public ViewHolder(View view) {
        super(view);
        circle = view.findViewById(R.id.circle);
        initial = (TextView) view.findViewById(R.id.initial);
        routineName = (TextView) view.findViewById(R.id.routine_name);
        lastTimeDone = (TextView) view.findViewById(R.id.last_time_done);
      }
    }
}

Fragment_routines_list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:minHeight="@dimen/standard_list_item_height"
  android:paddingBottom="8dp"
  android:background="@drawable/darker_background_on_pressed"
  android:clickable="true">
    ......
</RelativeLayout>
我做错了什么导致默认的删除动画无法工作?
8个回答

62

从 RecyclerView 中删除项目的正确方法是从数据集中删除该项目,然后告诉适配器该项目已被删除,如下所示:

myDataset.remove(position); // myDataset is List<MyObject>
mAdapter.notifyItemRemoved(position);

39

问题已解决。

问题出在,调用mAdapter.remove(position)之后,我的代码的另一个部分调用了mAdapter.notifyDataSetChanged(),我认为这会停止移除动画。

总之,如果您在进行动画时调用mAdapter.notifyDataSetChanged,则动画将停止。


11
如果你不这样做,索引会混乱。 - milosmns
7
解决这个问题的方法是在onClick中使用viewHolder.getAdapterPosition(),而不是在onBindViewHolder中传递的position。 - MobileMon

14
请使用notifyItemRemoved(position)代替notifyDataSetChanged(),代码如下:
myDataset.remove(position);
notifyItemRemoved(position);

因为notifyDataSetChanged()仅仅通知数据更新而没有任何动画效果。

1
但是适配器将会丢失列表中其他项目的位置,当您尝试删除另一个项目时,它将删除其下方的项目,如果下方没有项目,则会崩溃。请确认。 - Wale
1
使用以下代码,可以解决你的问题:myDataset.remove(position); notifyItemRemoved(position); notifyItemRangeChanged(position, youDataSets.size()); - kalandar

13
我能够通过动画移除视图,并按以下方式更新索引:
在适配器中,
public boolean removeItem(int position) {
    if (data.size() >= position + 1) {
        data.remove(position);
        return true;
    }
    return false;
}

在删除视图时,调用

if (adapter.removeItem(position)) {
    adapter.notifyItemRemoved(position);
    adapter.notifyItemRangeChanged(position, adapter.getItemCount());
}

我使用了布尔方法来确保双击等操作不会导致崩溃。


6
在长时间的调试之后,我意识到我需要在我的适配器中添加setHasStableIds(true)并实现相关内容。
@Override
public long getItemId(int position) {
    return position;
}

之后移除动画,程序开始正常工作。


5

移除动画无法正常工作的另一个原因可能是RecyclerView的高度。请验证高度为match_parent而非wrap_content


1
这是另一个原因。插入动画可以使用wrap_content,但删除动画不行。有什么解决方法吗? - Alex Newman

1

虽然有些晚了,但可能对想要使用动画搜索项目的人有所帮助。

在您的活动或片段中使用以下代码:

yourAdapter.animateTo(filteredModelList);

在您的RecyclerAdapter类中使用以下代码。
public void animateTo(List<CommonModel> models) {
        applyAndAnimateRemovals(models);
        applyAndAnimateAdditions(models);
        applyAndAnimateMovedItems(models);
    }

    private void applyAndAnimateRemovals(List<CommonModel> newModels) {
        for (int i = items.size() - 1; i >= 0; i--) {
            final CommonModel model = items.get(i);
            if (!newModels.contains(model)) {
                removeItem(i);
            }
        }
    }

    private void applyAndAnimateAdditions(List<CommonModel> newModels) {
        for (int i = 0, count = newModels.size(); i < count; i++) {
            final CommonModel model = newModels.get(i);
            if (!items.contains(model)) {
                addItem(i, model);
            }
        }
    }

    private void applyAndAnimateMovedItems(List<CommonModel> newModels) {
        for (int toPosition = newModels.size() - 1; toPosition >= 0; toPosition--) {
            final CommonModel model = newModels.get(toPosition);
            final int fromPosition = items.indexOf(model);
            if (fromPosition >= 0 && fromPosition != toPosition) {
                moveItem(fromPosition, toPosition);
            }
        }
    }

    private CommonModel removeItem(int position) {
        final CommonModel model = items.remove(position);
        notifyItemRemoved(position);
        return model;
    }

    private void addItem(int position, CommonModel model) {
        items.add(position, model);
        notifyItemInserted(position);
    }

    private void moveItem(int fromPosition, int toPosition) {
        final CommonModel model = items.remove(fromPosition);
        items.add(toPosition, model);
        notifyItemMoved(fromPosition, toPosition);
    }

0

我遇到了同样的问题,我通过实现自己的RecyclerView来解决这个问题,在我的RecyclerView中,我做了以下操作:

public class MyRecyclerView extends RecyclerView {
   private View mEmptyView;
   private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
       public void onChanged() {
          super.onChanged();
          updateEmptyView();
       }

       public void onItemRangeRemoved(int positionStart, int itemCount) {
           super.onItemRangeRemoved(positionStart, itemCount);
           updateEmptyView();
       }

       public void onItemRangeInserted(int positionStart, int itemCount) {
          super.onItemRangeInserted(positionStart, itemCount);
          updateEmptyView();
       }
     };

    // private void setAdapter() {}

    private void updateEmptyView() {
         // update empty view's visibility
    }

}

基本上,当您将项目添加/删除到RecyclerView中时,可以调用notifyItemInserted() / notifyItemRemoved()和notifyItemRangeChanged()方法,这些方法将在mDataObserver中调用onItemRangeRemoved() / onItemRangeInserted()。因此,在这些方法中,您可以更新空视图的可见性,并且不会破坏动画效果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接