No animation on item removal on RecyclerView

AndroidAndroid Recyclerview

Android Problem Overview


I am using RecyclerView for the first time. Everything is working fine except that there is no animation on item removal even though the animation on item addition works just fine.

I have not set any custom item animator, but according to the documentation:

> Animations for adding and removing items are enabled by default in RecyclerView.

So the animations on removal should work.

I would like to have the default animation on removal, but can't get that to work.

This is how I setup the 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);
}

This is my adapter:

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>

What am I doing wrong that causes the default removal animation to not work?

Android Solutions


Solution 1 - Android

The proper way to remove an item from recycler view is to remove the item from data set and then telling the adapter that the item is removed like so

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

Solution 2 - Android

Solved it.

The issue was that, after calling mAdapter.remove(position), another part of my code was calling mAdapter.notifyDataSetChanged() which I assume stops the removal animation.

To sum up, if you call mAdapter.notifyDataSetChanged while there is an animation ongoing the animation will stop.

Solution 3 - Android

Use notifyItemRemoved(position) instead of notifyDataSetChanged() like below

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

because notifyDataSetChanged() simply notifies the updated data without any animations.

Solution 4 - Android

I was able to remove the view with the animation and updated indices as follows:

Within the adapter,

public boolean removeItem(int position) {
    if (data.size() >= position + 1) {
        data.remove(position);
        return true;
    }
    return false;
}

While removing the views, call

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

I have used a boolean method to ensure that double clicks,etc. don't cause a crash.

Solution 5 - Android

after long debugging I realized I had to add setHasStableIds(true) to my adapter and implement

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

after that remove animation began to work

Solution 6 - Android

Another reason for a not properly working remove animation could be the RecyclerViews height. Verify that the height is match_parent and NOT wrap_content!

Solution 7 - Android

Late but might be helpful to someone, who want {search items with animation}

Use below code In your activity or fragment where

yourAdapter.animateTo(filteredModelList);

Use below code in your RecyclerAdapter class

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

Solution 8 - Android

I ran into same issue, and I fixed this by implementing my own RecyclerView, and in my recyclerview, I did this:

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
    }

}

Basically, when you add/remove item into/from recyclerview, you can call notifyItemInserted()/ notifyItemRemoved() and notifyItemRangeChanged(), these method will invoke onItemRangeRemoved() / onItemRangeInserted() in mDataObserver. So in these method, you can update empty view's visibility, and it will not break animations.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJordi Chac&#243;nView Question on Stackoverflow
Solution 1 - AndroidMuch OverflowView Answer on Stackoverflow
Solution 2 - AndroidJordi ChacónView Answer on Stackoverflow
Solution 3 - AndroidPraveen BalajiView Answer on Stackoverflow
Solution 4 - AndroidmUser1990View Answer on Stackoverflow
Solution 5 - AndroidnetimenView Answer on Stackoverflow
Solution 6 - Androiduser1185087View Answer on Stackoverflow
Solution 7 - AndroidAdeeb karimView Answer on Stackoverflow
Solution 8 - AndroidChengView Answer on Stackoverflow