让RecyclerView与众不同 - 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库有了一个深刻的了解,基于此基础可以实现各种炫酷的动画效果。
自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?
没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。