DefaultItemAnimator是Android OS中一个默认的RecyclerView动画实现类,如果产品需求没有特别复杂的动画要求,可以使用DefaultItemAnimator实现简单的动画效果。DefaultItemAnimator动画的实现流程和原理已经在上两节中做过简单介绍,如果还没有看过的童鞋,最好先打眼扫一下之前两节的内容,有助于理解。附上链接地址:
1 RecyclerView.ItemAnimator终极解读(一)--RecyclerView源码解析
2 RecyclerView.ItemAnimator终极解读(二)--SimpleItemAnimator和DefaultItemAnimator源码解析
这一节,我们主要通过继承DefaultItemAnimator来实现稍微复杂一点的自定义动画。效果如下两张图所示:
如上两张图所示:每点击一个item时,更新所点击item的背景颜色和文本信息。 第一张图是没有添加动画效果, 第二张图是添加自定义动画之后的效果。
主要代码就是在自定义的动画类MyDefaultItemAnimator.java中, 如下所示:
package material.danny_jiang.com.mydefaultitemanimator;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.NonNull;
import android.support.v4.util.ArrayMap;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
/**
* Created by axing on 16/5/25.
*/
public class MyDefaultItemAnimator extends DefaultItemAnimator {
private static final String TAG = "MyDefaultItemAnimator";
// 定义动画执行时的加速度
private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
private DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
ArgbEvaluator mColorEvaluator = new ArgbEvaluator();
/**
* 定义正在执行Animator的ViewHolder的Map集合
* 此集合会保存用户点击的ViewHolder对象,目的在于当用户不停的点击某一item时
* 会先判断此ViewHolder种的itemView动画是否正在执行,如果正在执行则停止
*/
private ArrayMap mAnimatorMap = new ArrayMap<>();
/**
* 复写canReuseUpdatedViewHolder方法并返回true,通知RecyclerView在执行动画时可以复用ViewHolder对象
* @param viewHolder
* @return
*/
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
/**
* 自定义getItemHolderInfo方法,将ViewHolder中的背景颜色和TextView的文本信息传入ColorTextInfo中
* @param viewHolder
* @param info
* @return
*/
@NonNull
private ItemHolderInfo getItemHolderInfo(MyViewHolder viewHolder, ColorTextInfo info) {
//获取当前正在操作的ViewHolder对象
final MyViewHolder myHolder = viewHolder;
//获取ViewHolder中itemView背景颜色
final int bgColor = ((ColorDrawable) myHolder.container.getBackground()).getColor();
//将背景颜色和TextView的文本信息赋值给ColorTextInfo对象的color和text变量
info.color = bgColor;
info.text = (String) myHolder.textView.getText();
return info;
}
/**
* 通过ViewHolder对象获取动画执行之前itemView中的背景颜色和文本信息
* 初始化ColorTextInfo对象,并将背景颜色和文本信息进行赋值
* @return
*/
@NonNull
@Override
public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
RecyclerView.ViewHolder viewHolder, int changeFlags, List payloads) {
Log.e(TAG, "recordPreLayoutInformation: " + viewHolder.toString());
ColorTextInfo info = (ColorTextInfo) super.recordPreLayoutInformation(state, viewHolder,
changeFlags, payloads);
return getItemHolderInfo((MyViewHolder) viewHolder, info);
}
/**
* 通过ViewHolder对象获取动画执行之后itemView中的背景颜色和文本信息
* 初始化ColorTextInfo对象,并将背景颜色和文本信息进行赋值
* @return
*/
@NonNull
@Override
public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state,
@NonNull RecyclerView.ViewHolder viewHolder) {
Log.e(TAG, "recordPostLayoutInformation: " + viewHolder.toString());
ColorTextInfo info = (ColorTextInfo) super.recordPostLayoutInformation(state, viewHolder);
return getItemHolderInfo((MyViewHolder) viewHolder, info);
}
/**
* 复写obtainHolderInfo,返回自定义的ItemHolderInfo对象
* @return
*/
@Override
public ItemHolderInfo obtainHolderInfo() {
Log.e(TAG, "obtainHolderInfo: ");
return new ColorTextInfo();
}
/**
* 自定义ItemHolderInfo对象,持有两个变量,依次来表示每一个Item的背景颜色和文本信息
*/
private class ColorTextInfo extends ItemHolderInfo {
int color;
String text;
}
/**
* 创建执行animateChange的动画Info对象,内部封装了所需要执行一个动画类的相关信息
* 起始alpha属性动画,和起始旋转属性动画
*/
private class AnimatorInfo {
Animator overallAnim;
ObjectAnimator fadeToBlackAnim, fadeFromBlackAnim, oldTextRotator, newTextRotator;
public AnimatorInfo(Animator overallAnim,
ObjectAnimator fadeToBlackAnim, ObjectAnimator fadeFromBlackAnim,
ObjectAnimator oldTextRotator, ObjectAnimator newTextRotator) {
this.overallAnim = overallAnim;
this.fadeToBlackAnim = fadeToBlackAnim;
this.fadeFromBlackAnim = fadeFromBlackAnim;
this.oldTextRotator = oldTextRotator;
this.newTextRotator = newTextRotator;
}
}
/**
* Custom change animation. Fade to black on the container background, then back
* up to the new bg coolor. Meanwhile, the text rotates, switching along the way.
* If a new change animation occurs on an item that is currently animating
* a change, we stop the previous change and start the new one where the old
* one left off.
* 真正的执行change动画的方法:
* 通过传入的preInfo和postInfo,分别将动画前后的背景色和文本信息设置到alpha属性动画和旋转属性动画中
*/
@Override
public boolean animateChange(@NonNull final RecyclerView.ViewHolder oldHolder,
@NonNull final RecyclerView.ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
Log.e(TAG, "animateChange: ");
if (oldHolder != newHolder) {
//第一次显示所有的RecyclerView时,新旧ViewHolder是不相等的
return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
}
final MyViewHolder viewHolder = (MyViewHolder) newHolder;
// 获取动画前后的背景色和文本信息
ColorTextInfo oldInfo = (ColorTextInfo) preInfo;
ColorTextInfo newInfo = (ColorTextInfo) postInfo;
int oldColor = oldInfo.color;
int newColor = newInfo.color;
final String oldText = oldInfo.text;
final String newText = newInfo.text;
// 获取需要被执行动画的View视图对象
LinearLayout newContainer = viewHolder.container;
final TextView newTextView = viewHolder.textView;
// 从mAnimatorMap缓存中查找当前newHolder对应的itemView动画是否在执行中,如果是则终止动画
AnimatorInfo runningInfo = mAnimatorMap.get(newHolder);
long prevAnimPlayTime = 0;
boolean firstHalf = false;
if (runningInfo != null) {
firstHalf = runningInfo.oldTextRotator != null &&
runningInfo.oldTextRotator.isRunning();
prevAnimPlayTime = firstHalf ?
runningInfo.oldTextRotator.getCurrentPlayTime() :
runningInfo.newTextRotator.getCurrentPlayTime();
runningInfo.overallAnim.cancel();
}
// 初始化背景颜色渐变的属性动画
ObjectAnimator fadeToBlack = null, fadeFromBlack;
if (runningInfo == null || firstHalf) {
int startColor = oldColor;
if (runningInfo != null) {
startColor = (Integer) runningInfo.fadeToBlackAnim.getAnimatedValue();
}
fadeToBlack = ObjectAnimator.ofInt(newContainer, "backgroundColor",
startColor, Color.BLACK);
fadeToBlack.setEvaluator(mColorEvaluator);
if (runningInfo != null) {
fadeToBlack.setCurrentPlayTime(prevAnimPlayTime);
}
}
fadeFromBlack = ObjectAnimator.ofInt(newContainer, "backgroundColor",
Color.BLACK, newColor);
fadeFromBlack.setEvaluator(mColorEvaluator);
if (runningInfo != null && !firstHalf) {
fadeFromBlack.setCurrentPlayTime(prevAnimPlayTime);
}
AnimatorSet bgAnim = new AnimatorSet();
if (fadeToBlack != null) {
bgAnim.playSequentially(fadeToBlack, fadeFromBlack);
} else {
bgAnim.play(fadeFromBlack);
}
// 初始化旋转的属性动画
ObjectAnimator oldTextRotate = null, newTextRotate;
if (runningInfo == null || firstHalf) {
oldTextRotate = ObjectAnimator.ofFloat(newTextView, View.ROTATION_X, 0, 90);
oldTextRotate.setInterpolator(mAccelerateInterpolator);
if (runningInfo != null) {
oldTextRotate.setCurrentPlayTime(prevAnimPlayTime);
}
oldTextRotate.addListener(new AnimatorListenerAdapter() {
boolean mCanceled = false;
@Override
public void onAnimationStart(Animator animation) {
newTextView.setText(oldText);
}
@Override
public void onAnimationCancel(Animator animation) {
mCanceled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (!mCanceled) {
//old动画执行之后,需要重新设置文本信息
newTextView.setText(newText);
}
}
});
}
newTextRotate = ObjectAnimator.ofFloat(newTextView, View.ROTATION_X, -90, 0);
newTextRotate.setInterpolator(mDecelerateInterpolator);
if (runningInfo != null && !firstHalf) {
newTextRotate.setCurrentPlayTime(prevAnimPlayTime);
}
AnimatorSet textAnim = new AnimatorSet();
if (oldTextRotate != null) {
textAnim.playSequentially(oldTextRotate, newTextRotate);
} else {
textAnim.play(newTextRotate);
}
AnimatorSet changeAnim = new AnimatorSet();
changeAnim.playTogether(bgAnim, textAnim);
changeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchAnimationFinished(newHolder);
mAnimatorMap.remove(newHolder);
}
});
changeAnim.start();
AnimatorInfo runningAnimInfo = new AnimatorInfo(changeAnim, fadeToBlack, fadeFromBlack,
oldTextRotate, newTextRotate);
mAnimatorMap.put(newHolder, runningAnimInfo);
return true;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
super.endAnimation(item);
if (!mAnimatorMap.isEmpty()) {
final int numRunning = mAnimatorMap.size();
for (int i = numRunning; i >= 0; i--) {
if (item == mAnimatorMap.keyAt(i)) {
mAnimatorMap.valueAt(i).overallAnim.cancel();
}
}
}
}
@Override
public boolean isRunning() {
return super.isRunning() || !mAnimatorMap.isEmpty();
}
@Override
public void endAnimations() {
super.endAnimations();
if (!mAnimatorMap.isEmpty()) {
final int numRunning = mAnimatorMap.size();
for (int i = numRunning; i >= 0; i--) {
mAnimatorMap.valueAt(i).overallAnim.cancel();
}
}
}
}
每段代码的含义的用途都已经在代码中添加了相应的注释,请耐得住寂寞,仔细查看^_^
自定义ItemAnimator实现好之后,剩下的就是初始化RecyclerView,并通过setItemAnimator方法将自定义ItemAnimator对象传递给RecyclerView。
MainActivity.java中的相关代码如下:
recyclerView = ((RecyclerView) findViewById(R.id.recyclerview));
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new RecyclerAdapter(this, recyclerView));
recyclerView.setItemAnimator(new MyDefaultItemAnimator());
RecyclerAdapter.java代码如下:
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final MyViewHolder myHolder = (MyViewHolder) holder;
int color = mColors.get(position);
myHolder.container.setBackgroundColor(color);
myHolder.textView.setText("#" + Integer.toHexString(color));
}
@Override
public int getItemCount() {
return mColors.size();
}
private View.OnClickListener mItemAction = new View.OnClickListener() {
@Override
public void onClick(View v) {
changeItem(v);
}
};
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View container = mainActivity.getLayoutInflater().inflate(R.layout.item_layout, parent, false);
container.setOnClickListener(mItemAction);
return new MyViewHolder(container);
}
private int generateColor() {
int red = ((int) (Math.random() * 200));
int green = ((int) (Math.random() * 200));
int blue = ((int) (Math.random() * 200));
return Color.rgb(red, green, blue);
}
private void generateData() {
for (int i = 0; i < 100; ++i) {
mColors.add(generateColor());
}
}
private void changeItem(View view) {
int position = mRecyclerView.getChildAdapterPosition(view);
if (position != RecyclerView.NO_POSITION) {
int color = generateColor();
mColors.set(position, color);
notifyItemChanged(position);
}
}
MyViewHolder.java代码如下:
package material.danny_jiang.com.mydefaultitemanimator;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* Created by axing on 16/5/25.
*/
class MyViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public LinearLayout container;
public MyViewHolder(View v) {
super(v);
container = (LinearLayout) v;
textView = (TextView) v.findViewById(R.id.textview);
}
@Override
public String toString() {
return super.toString() + " \"" + textView.getText() + "\"";
}
}