recyclerview-animators,让你的RecyclerView与众不同

RecyclerView已经普及使用,其各式各样的布局格式,以及众多的优越特性,使得RecyclerView具有很大的灵活性。其中之一便是ItemAnimator,通过自定义ItemAnimator可以实现各种各样的Item增加,删除,改变,移动等动画效果。这也是本篇文章的主要内容。

一、recyclerview-animators

本篇文章将围绕recyclerview-animators展开,先介绍其简单使用,然后介绍其实现原理。

首先,介绍recyclerview-animators的效果图。GitHub地址:recyclerview-animators

这个库主要实现两部分内容。

一:自定义ItemAnimator,实现Item增加和删除的动画效果。

二:对Adapter进行封装,在onBindViewHolder中,绑定过程中设置显示动画。

效果图

自定义ItemAnimator效果

Adapter动画效果

二、recyclerview-animators的使用

ItemAnimator的使用

1.引入依赖
compile 'jp.wasabeef:recyclerview-animators:2.2.6'
2.使用
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
recyclerView.setItemAnimator(new SlideInLeftAnimator());
3.高级功能

动画时长

recyclerView.getItemAnimator().setAddDuration(1000);
recyclerView.getItemAnimator().setRemoveDuration(1000);
recyclerView.getItemAnimator().setMoveDuration(1000);
recyclerView.getItemAnimator().setChangeDuration(1000);

插值器

SlideInLeftAnimator animator = new SlideInLeftAnimator();
animator.setInterpolator(new OvershootInterpolator());
recyclerView.setItemAnimator(animator);

另外自定义动画实现

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.3f)
.alpha(0)
.setDuration(300)
.setListener(listener)
.start();
}

@Override
public void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
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();
}
}

4.注意

使用以下方式,才会触发动画效果,具体下面分析:

notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)

例如:

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

public void add(String text, int position) {
mDataSet.add(position, text);
notifyItemInserted(position);
}

Adapter的使用

1.使用
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
MyAdapter adapter = new MyAdapter();
recyclerView.setAdapter(new AlphaInAnimationAdapter(adapter));
2.高级功能

动画时长

MyAdapter adapter = new MyAdapter();
AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
alphaAdapter.setDuration(1000);
recyclerView.setAdapter(alphaAdapter);

插值器

MyAdapter adapter = new MyAdapter();
AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
alphaAdapter.setInterpolator(new OvershootInterpolator());
recyclerView.setAdapter(alphaAdapter);

是否仅显示一次动画效果

MyAdapter adapter = new MyAdapter();
AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
scaleAdapter.setFirstOnly(false);
recyclerView.setAdapter(alphaAdapter);

复合动画

MyAdapter adapter = new MyAdapter();
AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
recyclerView.setAdapter(new ScaleInAnimationAdapter(alphaAdapter));

三、自定义ItemAnimator实现原理

上面我们分析了recyclerview-animators的实现,那么我们如何实现自己想要的更炫酷的动画方式呢,以及上述注意中为什么只能使用那几种方式才能实现动画效果呢?这一切都需要理解其原理才行,下面我们主要分析自定义动画的实现。

1.类结构

首先,所有的自定义ItemAnimator都是继承BaseItemAnimator实现的。

public abstract class BaseItemAnimator extends SimpleItemAnimator

而SimpleItemAnimator 又是什么? 是RecyclerView中根据ItemAnimator进行的一些简单的封装。

abstract public class SimpleItemAnimator extends RecyclerView.ItemAnimator

RecyclerView有着默认的动画效果实现DefaultItemAnimator,也继承了SimpleItemAnimator ,后面具体比较分析。

一切的源头都是ItemAnimator,所以先来了解下它的主要方法。

2.ItemAnimator

当RecyclerView中的item在屏幕上由可见变为不可见时调用此方法

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

当RecyclerView中的item显示到屏幕上时调用此方法

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

当RecyclerView中的item状态发生改变时调用此方法(notifyItemChanged(position))

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

统筹RecyclerView中所有的动画,统一启动执行

abstract public void runPendingAnimations();

这几个方法是自定义ItemAnimator的关键,实现不同的动画效果。

SimpleItemAnimator 在ItemAnimator的基础上,针对上述几个方法,进行了一定的封装,使得可以更专注于动画的实现。

3.SimpleItemAnimator

SimpleItemAnimator对这几个方法是如何封装的呢?
以添加一个Item举例,最后调用的是animateAppearance方法,SimpleItemAnimator的animateAppearance方法代码如下:

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);
}
return animateRemove(viewHolder);
}
}

其实封装的很简单,只是判断了一下布局前后两个ViewHolder的left/top坐标是否相等 如果不相等则调用animateMove,否则调用animateAdd(viewHolder)方法。

其他方法类似,最后我们只需要实现animateRemove, animateAdd, animateMove, animateChange 这4个方法。

下面我们先看看RecyclerView中DefaultItemAnimator的实现,再来分析BaseItemAnimator。

4.DefaultItemAnimator

首先是对animateRemove, animateAdd, animateMove, animateChange 这4个方法的实现。
还是以animateAdd为例:

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

首先调用resetAnimation(holder)停止此holder中itemView的动画效果,然后将holder.itemView的透明度设置为0不可见状态(也可以设置其他属性,相当于动画开始的初始效果)。
将holder添加到mPendingAdditions集合中。

注意:此集合一会儿会在runPendingAnimations方法中进行遍历执行动画。

然后是对runPendingAnimations的实现。
下面上源码,很长,不过别害怕,很简单。

public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
// First, remove stuff
for (ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
if (movesPending) {
final ArrayList<MoveInfo> moves = new ArrayList<>();
moves.addAll(mPendingMoves);
mMovesList.add(moves);
mPendingMoves.clear();
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();
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();
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
changes.clear();
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();
Runnable adder = new Runnable() {
@Override
public void run() {
for (ViewHolder holder : additions) {
animateAddImpl(holder);
}
additions.clear();
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();
}
}
}

remove最先执行,for循环遍历mPendingRemovals集合中所有的ViewHolder。remove完成后,再同时开始move和change动画,最后执行add动画。

其中,需要注意几个重要的方法:
animateRemoveImpl(holder);
animateMoveImpl(moveInfo.holder,moveInfo.fromX, moveInfo.fromY,moveInfo.toX, moveInfo.toY);
animateChangeImpl(change);
animateAddImpl(holder);

这几个方法执行具体的动画效果,具体来看源码。

下面来分析下BaseItemAnimator的实现。

5.BaseItemAnimator

了解了DefaultItemAnimator,那么对BaseItemAnimator的实现就清楚了。

首先BaseItemAnimator使用了DefaultItemAnimator中的runPendingAnimations()方法的实现。
其次BaseItemAnimator使用了DefaultItemAnimator中的animateMove, animateChange,以及animateRemoveImpl,animateChangeImpl等方法的实现。

所以BaseItemAnimator只留下了animateRemove, animateAdd方法,方便我们自定义实现。

以animateAdd方法为例:

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

第一步:停止此holder中itemView的动画效果,与DefaultItemAnimator一致。

第二步:初始化itemView的属性

第三步:将holder添加到mPendingAdditions集合中。

与DefaultItemAnimator的不同主要是第二步,详细看看。

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

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

如果holder属于AnimateViewHolder类或子类,那么就调用其preAnimateAddImpl()方法,否则调用内部的preAnimateAddImpl()方法。

protected void preAnimateAddImpl(final ViewHolder holder) {
}

该方法空实现,由子类负责实现,初始化定义各种动画初始属性。

关于AnimateViewHolder类,前面说过,可以通过自定义实现该接口,实现各种动画效果来代替具体的ItemAnimator,其内部需要实现的方法,与继承BaseItemAnimator的子类需要实现的方法一样。

除此之外,针对add,在runPendingAnimations()方法中,需要指定具体的动画效果。

截取runPendingAnimations()方法中的add部分。

if (additionsPending) {
final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
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();
}
};

其中重要的一行就是: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);
}

和上面差不多,其中animateAddImpl()方法空实现,需要子类实现。

那么子类需要实现四个方法,就能完成自定义动画了,是不是很简单?

6.具体实例

下面举一个实现具体动画的例子:

public class FadeInAnimator extends BaseItemAnimator {

public FadeInAnimator() {
}

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(new DefaultAddVpaListener(holder))
.setStartDelay(getAddDelay(holder))
.start();
}
}

很简单吧,这样我们就可以任意自定义各种增删item的效果了。

四、Adapter动画效果实现原理

主要是用了装饰者模式,对原来的adapter进行了一层封装,增加了额外的功能。

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

把原始的adapter传入了具有动画效果的新的Adpter中,那么新的Adpter内部是怎么实现的?
以AlphaInAnimationAdapter为例:

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

可见其内部主要提供了getAnimators方法,定义动画效果,其他方法来自于AnimationAdapter。

AnimationAdapter中主要是通过onBindViewHolder来增加动画效果

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

循环遍历每个item,增加动画效果。

五、总结

通过上面的分析,对recyclerview-animators库有了一个深刻的了解,基于此基础可以实现各种炫酷的动画效果。

参考文章:

深入理解 RecyclerView 系列之二:ItemAnimator
RecyclerView.ItemAnimator终极解读(一)–RecyclerView源码解析
RecyclerView.ItemAnimator终极解读(二)–SimpleItemAnimator和DefaultItemAnimator源码解析
RecyclerView.ItemAnimator终极解读(三)–继承DefaultItemAnimator实现自定义动画