RecyclerView has been widely used, its various layout formats, as well as many superior features, make RecyclerView has great flexibility. One of them is the ItemAnimator, which allows you to animate various items by adding, removing, changing, and moving them. This is the main content of this article.

A, recyclerview – animators

This article will focus on recyclerview-animators, first introduce its simple use, and then introduce its realization principle.

First of all, introduced recyclerView-animators renderings. GitHub recyclerView-animators

There are two main parts to this library.

1. Customize ItemAnimator to add and delete items.

Two: Encapsulate the Adapter. In onBindViewHolder, display animation is set during binding.

rendering

Customize the ItemAnimator effect




Adapter Animation Effects




Two, recyclerview-animators use

The use of ItemAnimator

1. Introduce dependencies
 compile 'jp. Wasabeef: recyclerview - animators: 2.2.6'Copy the code
2. Use
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
recyclerView.setItemAnimator(new SlideInLeftAnimator());Copy the code
3. Advanced features

Animation duration

recyclerView.getItemAnimator(a).setAddDuration(1000);
recyclerView.getItemAnimator(a).setRemoveDuration(1000);
recyclerView.getItemAnimator(a).setMoveDuration(1000);
recyclerView.getItemAnimator(a).setChangeDuration(1000);Copy the code

interpolator

SlideInLeftAnimator animator = new SlideInLeftAnimator(a); animator.setInterpolator(new OvershootInterpolator());
recyclerView.setItemAnimator(animator);Copy the code

Also custom animation implementation

static class MyViewHolder extends RecyclerView.ViewHolder implements AnimateViewHolder {
  public MyViewHolder(View itemView) {
    super(itemView);
  }

  @Override
  public void preAnimateRemoveImpl(RecyclerView.ViewHolder holder) {}@Override
  public void animateRemoveImpl(RecyclerView.ViewHolder holder, ViewPropertyAnimatorListener listener) {
    ViewCompat.animate(itemView)
          .translationY(-itemView.getHeight() * 0.3 f)
          .alpha(0)
          .setDuration(300)
          .setListener(listener)
          .start();
  }

  @Override
  public void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
    ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3 f);
    ViewCompat.setAlpha(itemView, 0);
  }

  @Override
  public void animateAddImpl(RecyclerView.ViewHolder holder, ViewPropertyAnimatorListener listener) {
    ViewCompat.animate(itemView)
          .translationY(0)
          .alpha(1)
          .setDuration(300) .setListener(listener) .start(); }}Copy the code
4. Pay attention to

Use the following way, will trigger the animation effect, specific analysis below:

notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)Copy the code

Such as:

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

public void add(String text.int position) {
  mDataSet.add(position.text);
  notifyItemInserted(position);
}Copy the code

The use of the Adapter

1. Use
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
MyAdapter adapter = new MyAdapter(a); recyclerView.setAdapter(new AlphaInAnimationAdapter(adapter));Copy the code
2. Advanced features

Animation duration

MyAdapter adapter = new MyAdapter(a);AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
alphaAdapter.setDuration(1000);
recyclerView.setAdapter(alphaAdapter);Copy the code

interpolator

MyAdapter adapter = new MyAdapter(a); AlphaInAnimationAdapter alphaAdapter =new AlphaInAnimationAdapter(adapter);
alphaAdapter.setInterpolator(new OvershootInterpolator());
recyclerView.setAdapter(alphaAdapter);Copy the code

Whether to show animation effects only once

MyAdapter adapter = new MyAdapter(a); AlphaInAnimationAdapter alphaAdapter =new AlphaInAnimationAdapter(adapter);
scaleAdapter.setFirstOnly(false);
recyclerView.setAdapter(alphaAdapter);Copy the code

Composite animation

MyAdapter adapter = new MyAdapter(a); AlphaInAnimationAdapter alphaAdapter =new AlphaInAnimationAdapter(adapter);
recyclerView.setAdapter(new ScaleInAnimationAdapter(alphaAdapter));Copy the code

3. The implementation principle of custom ItemAnimator

Above we analyzed the recyclerView-animators implementation, so how do we achieve their own cool animation way, and the above attention why can only use that several ways to achieve animation effect? All of this requires an understanding of how it works, so let’s focus on the implementation of custom animations.

1. Class structure

First, all custom ItemAnimators inherit from the BaseItemAnimator implementation.

public abstract class BaseItemAnimator extends SimpleItemAnimatorCopy the code

And what is the SimpleItemAnimator? Is a simple wrapper in RecyclerView according to ItemAnimator.

abstract public class SimpleItemAnimator extends RecyclerView.ItemAnimatorCopy the code

RecyclerView has the default animation effect DefaultItemAnimator, also inherited from SimpleItemAnimator, the following specific comparison and analysis.

The source of everything is the ItemAnimator, so let’s take a look at its main methods.

2.ItemAnimator

Call this method when an item in RecyclerView changes from visible to invisible on the screen

 public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
                @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);Copy the code

This method is called when an item in RecyclerView is displayed on the screen

public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
                @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);Copy the code

Call notifyItemChanged(position) when the state of an item in RecyclerView changes

public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
                @NonNull ViewHolder newHolder,
                @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);Copy the code

Overall planning RecyclerView all animation, unified start execution

abstract public void runPendingAnimations(a);Copy the code

These methods are key to customizing the ItemAnimator to achieve different animation effects.

SimpleItemAnimator encapsulates the above methods in order to focus on animation implementation.

3.SimpleItemAnimator

How does SimpleItemAnimator encapsulate these methods? The animateAppearance method of SimpleItemAnimator is called as follows:

public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        int oldLeft = preLayoutInfo.left;
        int oldTop = preLayoutInfo.top;
        View disappearingItemView = viewHolder.itemView;
        int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
        int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
        if(! viewHolder.isRemoved() && (oldLeft ! =newLeft|| oldTop ! =newTop)) {
            disappearingItemView.layout(newLeft.newTop.newLeft + disappearingItemView.getWidth(),
                    newTop + disappearingItemView.getHeight());
            if (DEBUG) {
                Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
            }
            return animateMove(viewHolder, oldLeft, oldTop, newLeft.newTop);
        } else {
            if (DEBUG) {
                Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
            }
            returnanimateRemove(viewHolder); }}Copy the code

If the left/top coordinates of the ViewHolder are not equal, call animateMove. Otherwise, call animateAdd(ViewHolder).

The other methods are similar. In the end, we only need to implement animateRemove, animateAdd, animateMove, and animateChange.

Below we first look at the implementation of DefaultItemAnimator in RecyclerView, and then analyze BaseItemAnimator.

4.DefaultItemAnimator

The first is the implementation of animateRemove, animateAdd, animateMove, animateChange. Again, take animateAdd:

 public boolean animateAdd(final ViewHolder holder) {
        resetAnimation(holder);
        ViewCompat.setAlpha(holder.itemView, 0);
        mPendingAdditions.add(holder);
        return true;
    }Copy the code

First call resetAnimation(holder) to stop animating the itemView in this holder, and then set the transparency of hold. itemView to 0 (other properties can also be set, which corresponds to the initial effect of the beginning of the animation). Add holder to the mPendingAdditions collection.

Note: This collection will be iterated to perform animations in the runPendingAnimations method later.

Then there is the implementation of runPendingAnimations. Below the source code, very long, but don’t be afraid, very simple.

public void runPendingAnimations() {
        booleanremovalsPending = ! mPendingRemovals.isEmpty();booleanmovesPending = ! mPendingMoves.isEmpty();booleanchangesPending = ! mPendingChanges.isEmpty();booleanadditionsPending = ! mPendingAdditions.isEmpty();if(! removalsPending && ! movesPending && ! additionsPending && ! changesPending) {// nothing to animate
            return;
        }
        // First, remove stuff
        for (ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder);
        }
        mPendingRemovals.clear(a);// Next, move stuff
        if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear(a); Runnable mover =new Runnable() {
                @Override
                public void run() {
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                    moves.clear(a); mMovesList.remove(moves); }};if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }
        // Next, change stuff, to run in parallel with move animations
        if (changesPending) {
            final ArrayList<ChangeInfo> changes = new ArrayList<>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear(a); Runnable changer =new Runnable() {
                @Override
                public void run() {
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    changes.clear(a); mChangesList.remove(changes); }};if (removalsPending) {
                ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
        // Next, add stuff
        if (additionsPending) {
            final ArrayList<ViewHolder> additions = new ArrayList<>();
            additions.addAll(mPendingAdditions);
            mAdditionsList.add(additions);
            mPendingAdditions.clear(a); Runnable adder =new Runnable() {
                @Override
                public void run() {
                    for (ViewHolder holder : additions) {
                        animateAddImpl(holder);
                    }
                    additions.clear(a); mAdditionsList.remove(additions); }};if (removalsPending || movesPending || changesPending) {
                long removeDuration = removalsPending ? getRemoveDuration() : 0;
                long moveDuration = movesPending ? getMoveDuration() : 0;
                long changeDuration = changesPending ? getChangeDuration() : 0;
                long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
                View view = additions.get(0).itemView;
                ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
            } else {
                adder.run(a); }}}Copy the code

Remove is executed first, and the for loop iterates through all ViewHolder in the mPendingRemovals set. Once remove is complete, move and change animations begin at the same time, and finally add animations are performed.

There are several important methods to note: animateRemoveImpl(Holder); animateMoveImpl(moveInfo.holder,moveInfo.fromX, moveInfo.fromY,moveInfo.toX, moveInfo.toY); animateChangeImpl(change); animateAddImpl(holder);

These methods perform specific animation effects, see the source code.

Let’s look at the implementation of BaseItemAnimator.

5.BaseItemAnimator

Now that you know DefaultItemAnimator, the implementation of BaseItemAnimator is clear.

First, BaseItemAnimator uses the implementation of the runPendingAnimations() method in DefaultItemAnimator. Second, BaseItemAnimator uses DefaultItemAnimator in the animateMove, animateChange, and animateRemoveImpl, animateChangeImpl method implementation.

So BaseItemAnimator only left animateRemove, animateAdd method, easy to customize implementation.

Take the animateAdd method as an example:

 @Override public boolean animateAdd(final ViewHolder holder) {
    endAnimation(holder);
    preAnimateAdd(holder);
    mPendingAdditions.add(holder);
    return true;
  }Copy the code

Step 1: Stop animating the itemView in this holder, consistent with DefaultItemAnimator.

Step 2: Initialize the properties of itemView

Step 3: Add holder to the mPendingAdditions collection.

The main difference from DefaultItemAnimator is step 2, look at it in detail.

private void preAnimateAdd(final ViewHolder holder) {
    ViewHelper.clear(holder.itemView);

    if (holder instanceof AnimateViewHolder) {
      ((AnimateViewHolder) holder).preAnimateAddImpl(holder);
    } else{ preAnimateAddImpl(holder); }}Copy the code

If the holder belongs to the AnimateViewHolder class or subclass, then its preAnimateAddImpl() method is called, otherwise the internal preAnimateAddImpl() method is called.

protected void preAnimateAddImpl(final ViewHolder holder) {}Copy the code

This method is implemented by subclasses that initialize and define various initial properties of the animation.

With regard to the AnimateViewHolder class, as mentioned earlier, this interface can be customized to implement various animation effects in place of a specific ItemAnimator, with the same internal methods as those implemented by subclasses that inherit BaseItemAnimator.

In addition, in the runPendingAnimations() method for Add, specific animations need to be specified.

Intercept the Add part of the runPendingAnimations() method.

if (additionsPending) {
      final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
      additions.addAll(mPendingAdditions);
      mAdditionsList.add(additions);
      mPendingAdditions.clear(a); Runnable adder =new Runnable() {
        public void run() {
          boolean removed = mAdditionsList.remove(additions);
          if(! removed) {// already canceled
            return;
          }
          for (ViewHolder holder : additions) {
            doAnimateAdd(holder);
          }
          additions.clear();
        }
      };Copy the code

One important line is: doAnimateAdd(holder); .

private void doAnimateAdd(final ViewHolder holder) {
    if (holder instanceof AnimateViewHolder) {
      ((AnimateViewHolder) holder).animateAddImpl(holder, new DefaultAddVpaListener(holder));
    } else {
      animateAddImpl(holder);
    }

    mAddAnimations.add(holder);
  }Copy the code

As above, the animateAddImpl() method is empty and requires a subclass.

So the subclass needs to implement four methods to complete the custom animation, isn’t that easy?

6. Concrete examples

Here is an example of implementing a concrete animation:

public class FadeInAnimator extends BaseItemAnimator {

  public FadeInAnimator(a) {}public FadeInAnimator(Interpolator interpolator) {
    mInterpolator = interpolator;
  }

  @Override protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
    ViewCompat.animate(holder.itemView)
        .alpha(0)
        .setDuration(getRemoveDuration())
        .setInterpolator(mInterpolator)
        .setListener(new DefaultRemoveVpaListener(holder))
        .setStartDelay(getRemoveDelay(holder))
        .start();
  }

  @Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
    ViewCompat.setAlpha(holder.itemView, 0);
  }

  @Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
    ViewCompat.animate(holder.itemView)
        .alpha(1)
        .setDuration(getAddDuration())
        .setInterpolator(mInterpolator)
        .setListener(newDefaultAddVpaListener(holder)) .setStartDelay(getAddDelay(holder)) .start(); }}Copy the code

It’s very simple, so we can customize the effects of adding and removing items.

4. Realization principle of Adapter animation effect

The decorator mode is used to encapsulate the original Adapter and add additional functionality.

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
MyAdapter adapter = new MyAdapter(a); recyclerView.setAdapter(new AlphaInAnimationAdapter(adapter));Copy the code

If the original adapter is passed into a new Adpter with animated effects, how is the new Adpter implemented internally? Take AlphaInAnimationAdapter as an example:

public class AlphaInAnimationAdapter extends AnimationAdapter {

  private static final float DEFAULT_ALPHA_FROM = 0f;
  private final float mFrom;

  public AlphaInAnimationAdapter(RecyclerView.Adapter adapter) {
    this(adapter, DEFAULT_ALPHA_FROM);
  }

  public AlphaInAnimationAdapter(RecyclerView.Adapter adapter, float from) {
    super(adapter);
    mFrom = from;
  }

  @Override protected Animator[] getAnimators(View view) {
    return new Animator[] { ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)}; }}Copy the code

As you can see, the main internal method is getAnimators, which defines the animation effects. The other methods come from the AnimationAdapter.

AnimationAdapter the onBindViewHolder is used to add animation effects

 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    mAdapter.onBindViewHolder(holder, position);

    int adapterPosition = holder.getAdapterPosition();
    if(! isFirstOnly || adapterPosition > mLastPosition) {for (Animator anim : getAnimators(holder.itemView)) {
        anim.setDuration(mDuration).start();
        anim.setInterpolator(mInterpolator);
      }
      mLastPosition = adapterPosition;
    } else {
      ViewHelper.clear(holder.itemView); }}Copy the code

Loop through each item to add animation.

Five, the summary

Through the above analysis, recyclerView-Animators library has a deep understanding, based on this basis can achieve a variety of cool animation effects.

Reference article:

In-depth understanding of RecyclerView series two: ItemAnimator RecyclerView. ItemAnimator ultimate interpretation (a) – RecyclerView source code parsing RecyclerView. ItemAnimator ultimate interpretation (2) – SimpleItemAnimator and DefaultItemAnimator source code parsing RecyclerView. ItemAnimator ultimate interpretation (3) — — inherit DefaultItemAnimator implement custom animation