RecyclerView 上的项目删除没有动画

Posted

技术标签:

【中文标题】RecyclerView 上的项目删除没有动画【英文标题】:No animation on item removal on RecyclerView 【发布时间】:2015-01-27 01:24:06 【问题描述】:

我是第一次使用RecyclerView。一切正常,除了没有关于移除项目的动画,即使添加项目的动画效果很好。

我没有设置任何自定义的item animator,而是根据documentation:

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_
  android:layout_
  android:minHeight="@dimen/standard_list_item_height"
  android:paddingBottom="8dp"
  android:background="@drawable/darker_background_on_pressed"
  android:clickable="true">
    ......
</RelativeLayout>

我做错了什么导致默认删除动画不起作用?

【问题讨论】:

【参考方案1】:

从回收器视图中删除项目的正确方法是从数据集中删除该项目,然后告诉适配器该项目已被删除

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

【讨论】:

【参考方案2】:

解决了。

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

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

【讨论】:

如果你不这样做,索引会搞砸的。 解决这个问题的方法是使用 viewHolder.getAdapterPosition() 进行 onClick,而不是在 onBindViewHolder 内部传入位置【参考方案3】:

使用notifyItemRemoved(position) 代替notifyDataSetChanged(),如下所示

myDataset.remove(position);
notifyItemRemoved(position);

因为notifyDataSetChanged() 只是简单地通知更新的数据而没有任何动画。

【讨论】:

但是适配器会丢失列表中其他项目的位置,当你尝试删除另一个项目时,它会删除它下面的项目,如果它下面没有项目,那么它会崩溃。请确认。 myDataset.remove(position); notifyItemRemoved(位置); notifyItemRangeChanged(位置, youDataSets.size());使用此代码,这将解决您的问题。【参考方案4】:

我能够删除带有动画和更新索引的视图,如下所示:

在适配器内,

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());

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

【讨论】:

【参考方案5】:

经过长时间调试,我意识到我必须将setHasStableIds(true) 添加到我的适配器并实现

@Override
public long getItemId(int position) 
    return position;

之后移除动画开始起作用

【讨论】:

【参考方案6】:

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

【讨论】:

这是另一个原因。插入动画适用于wrap_content,但不适用于删除动画。有什么办法可以解决这个问题?【参考方案7】:

迟到但可能对想要搜索带有动画的项目的人有所帮助

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

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);
    

【讨论】:

【参考方案8】:

我遇到了同样的问题,我通过实现自己的 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(),这些方法将调用onItemRangeRemoved() / onItemRangeInserted() in mDataObserver。因此,在这些方法中,您可以更新空视图的可见性,并且不会破坏动画。

【讨论】:

以上是关于RecyclerView 上的项目删除没有动画的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView 移除动画bug

移除 Item RecyclerView 时添加自定义动画

如何将 videoView 与 imageView 和 recyclerView 一起制作动画?

精通RecyclerView:打造ListViewGridView瀑布流;学会添加分割线 添加删除动画 Item点击事件

Android L 中的 RecyclerView ItemAnimator 故障

RecyclerView notifyItemRemoved 的坑