今天进行属性动画实战第三个应用:实现抢红包动画
实现思路:
1、绘制红包
2、绘制抢红包进度背景
3、绘制抢红包进度
4、绘制爆炸效果形状
5、爆炸效果扩散透明动画
6、红包缩放动画
代码具体实现:
绘制红包:
//绘制红包
canvas.drawBitmap(mRedPackageBitmap,left,top,null);
绘制抢红包进度背景:
//绘制红包进度背景
//如果总进度为0,无需绘制红包进度背景
if (mTotalProgress == 0) {
return;
}
//当前红包进度不为0,绘制红包进度条
float progressWidth = mRedProgressBgBitmap.getWidth() * 0.8f;
float progressHeight = mRedProgressBgBitmap.getHeight() * 0.35f;
float progressRound = progressHeight / 2;
if (mCurrentProgress != 0) {
//绘制红包进度条
left = left * 3.4f;
top = top * 1.165f;
right = left + progressWidth / mTotalProgress * mCurrentProgress;
@SuppressLint("DrawAllocation")
RectF rect = new RectF(left, top, right, top + progressHeight);
// 设置进度条渐变颜色
@SuppressLint("DrawAllocation")
Shader shader = new LinearGradient(0, 0, progressWidth, 0,
new int[]{mProgressStarColor, mProgressEndColor},
new float[]{0, 1.0f}, Shader.TileMode.CLAMP);
mProgressPaint.setShader(shader);
canvas.drawRoundRect(rect, progressRound, progressRound, mProgressPaint);
}
绘制爆炸效果形状:
//绘制爆炸扩散效果
if (mCurrentBombRadiusChangeRate > 0 && mCurrentBombRadiusChangeRate < 1) {
//当前爆炸半径
float mCurrentBombRadius;
mCurrentBombRadius = mCurrentBombRadiusChangeRate * mBombRadius * 5.8f;
//画笔透明度是0~255之间的值
//mBombPaint.setAlpha((int) ((1-mCurrentBombRadiusChangeRate)*255));
mBombPaint.setAlpha((int) ((1.1 - mCurrentBombRadiusChangeRate) * 255));
float fixAngle = (float) (2 * Math.PI / mBombCount);
for (int i = 0; i < mBombCount; i++) {
float leftPosition, topPosition;
Bitmap bombShape = mRedBombShapes[i % 2];
leftPosition = (float) (right - bombShape.getWidth() / 2 + mCurrentBombRadius * Math.cos(fixAngle * i));
topPosition = (float) (top + progressHeight / 2 - bombShape.getHeight() / 2 + mCurrentBombRadius * Math.sin(fixAngle * i));
//topPosition = (float) (top - progressHeight/2 + mCurrentBombRadius*Math.sin(fixAngle*i));
canvas.drawBitmap(bombShape, leftPosition, topPosition, mBombPaint);
}
}
抢红包进度动画:
/**
* 开始动画
*
* @param from
* @param to
*/
private void startAnimator(float from, float to) {
ValueAnimator animator = ValueAnimator.ofFloat(from, to);
animator.setDuration(800);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(animation -> {
float currentProgress = (float) animation.getAnimatedValue();
setCurrentProgress(currentProgress);
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startExtendBombAnimator();
}
});
animator.start();
}
爆炸效果扩散透明动画:
/**
* 出现爆炸效果后,执行爆炸外扩,透明度渐变消失
*/
private void startExtendBombAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(ANIMATOR_DURATION);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(animation -> {
mCurrentBombRadiusChangeRate = (float) animation.getAnimatedValue();
invalidate();
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//爆炸扩散效果执行完需要进行红包进度加载
if (mCurrentProgress != mTotalProgress) {
startAnimator(mCurrentProgress,
mTotalProgress / mProgressBlockCount + mCurrentProgress);
} else {
startScaleSmallAnimator();
}
}
});
valueAnimator.start();
}
红包缩小动画:
/**
* 当前进度达到最大值,开始缩小动画
*/
private void startScaleSmallAnimator() {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 1, 0.8f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 1, 0.8f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATOR_DURATION);
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startScaleBigAnimator();
}
});
animatorSet.start();
}
红包放大动画:
/**
* 缩小动画结束后,开始放大
*/
private void startScaleBigAnimator() {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 0.8f, 1);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0.8f, 1);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATOR_DURATION);
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mScaleAnimatorCount--;
//如果缩放动画次数不为0,则进行缩放动画
if (mScaleAnimatorCount != 0) {
startScaleSmallAnimator();
} else {
mScaleAnimatorCount = 3;
mIsFinishAllAnimator = true;
}
}
});
animatorSet.start();
}
优化处理
/**
* 优化处理
*
* @param visibility
*/
@Override
public void setVisibility(int visibility) {
super.setVisibility(INVISIBLE);
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
parent.removeView(this);
}
clearAnimation();
}
控件使用
//设置总进度
redPackageView.setTotalProgress(4);
//开始抢红包动画
redPackageView.starAnimator();
完整代码
/**
* @author yifuyun
* @date 2020/5/14
* 自定义红包控件
*/
public class RedPackageView extends View {
private static final long ANIMATOR_DURATION = 300;
//爆炸形状数组
private final Bitmap[] mRedBombShapes;
//红包图片
private Bitmap mRedPackageBitmap;
//红包进度背景图片
private Bitmap mRedProgressBgBitmap;
//红包总进度
private float mTotalProgress = 3;
//当前红包进度
private float mCurrentProgress;
//红包进度条画笔
private Paint mProgressPaint;
//是否已经结束所有的动画
private boolean mIsFinishAllAnimator = true;
//红包进度条渐变色
private int mProgressStarColor = Color.parseColor("#FDA501");
private int mProgressEndColor = Color.parseColor("#FFEF04");
//显示爆炸效果总个数
private int mBombCount = 8;
//当前爆炸半径的变化率
private float mCurrentBombRadiusChangeRate;
//初始爆炸半径
private final float mBombRadius;
//进度分成节数
private float mProgressBlockCount = 3;
//爆炸效果画笔
private Paint mBombPaint;
//缩放动画次数
private int mScaleAnimatorCount = 3;
//进度动画
private ValueAnimator mProgressAnimator;
//爆炸扩散以及透明度动画
private ValueAnimator mExtendBombAnimator;
//缩放集合动画
private AnimatorSet mScaleAnimatorSet;
public RedPackageView(Context context) {
this(context, null);
}
public RedPackageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RedPackageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mRedPackageBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_red_package_normal);
mRedProgressBgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_red_package_pb_bg);
mRedBombShapes = new Bitmap[]{BitmapFactory.decodeResource(getResources(), R.mipmap.icon_red_package_bomb_1),
BitmapFactory.decodeResource(getResources(), R.mipmap.icon_red_package_bomb_2)};
mBombRadius = mRedProgressBgBitmap.getHeight() * 0.1f;
mProgressPaint = new Paint();
mProgressPaint.setDither(true);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setColor(Color.BLUE);
mBombPaint = new Paint();
mBombPaint.setDither(true);
mBombPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = (int) Math.max(mRedPackageBitmap.getWidth(), mRedPackageBitmap.getHeight() * 1.171);
setMeasuredDimension(size, size);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float left = 0;
float top = 0;
float right = 0;
//绘制红包
canvas.drawBitmap(mRedPackageBitmap, left, top, null);
//绘制红包进度背景
left = mRedProgressBgBitmap.getHeight() * 0.12f;
top = getHeight() - mRedProgressBgBitmap.getHeight() - left;
canvas.drawBitmap(mRedProgressBgBitmap, left, top, null);
//如果总进度为0,无需绘制红包进度背景
if (mTotalProgress == 0) {
return;
}
//当前红包进度不为0,绘制红包进度条
float progressWidth = mRedProgressBgBitmap.getWidth() * 0.8f;
float progressHeight = mRedProgressBgBitmap.getHeight() * 0.35f;
float progressRound = progressHeight / 2;
if (mCurrentProgress != 0) {
//绘制红包进度条
left = left * 3.4f;
top = top * 1.165f;
right = left + progressWidth / mTotalProgress * mCurrentProgress;
@SuppressLint("DrawAllocation")
RectF rect = new RectF(left, top, right, top + progressHeight);
// 设置进度条渐变颜色
@SuppressLint("DrawAllocation")
Shader shader = new LinearGradient(0, 0, progressWidth, 0,
new int[]{mProgressStarColor, mProgressEndColor},
new float[]{0, 1.0f}, Shader.TileMode.CLAMP);
mProgressPaint.setShader(shader);
canvas.drawRoundRect(rect, progressRound, progressRound, mProgressPaint);
}
//绘制爆炸扩散效果
if (mCurrentBombRadiusChangeRate > 0 && mCurrentBombRadiusChangeRate < 1) {
//当前爆炸半径
float mCurrentBombRadius;
mCurrentBombRadius = mCurrentBombRadiusChangeRate * mBombRadius * 5.8f;
//画笔透明度是0~255之间的值
//mBombPaint.setAlpha((int) ((1-mCurrentBombRadiusChangeRate)*255));
mBombPaint.setAlpha((int) ((1.1 - mCurrentBombRadiusChangeRate) * 255));
float fixAngle = (float) (2 * Math.PI / mBombCount);
for (int i = 0; i < mBombCount; i++) {
float leftPosition, topPosition;
Bitmap bombShape = mRedBombShapes[i % 2];
leftPosition = (float) (right - bombShape.getWidth() / 2 + mCurrentBombRadius * Math.cos(fixAngle * i));
topPosition = (float) (top + progressHeight / 2 - bombShape.getHeight() / 2 + mCurrentBombRadius * Math.sin(fixAngle * i));
//topPosition = (float) (top - progressHeight/2 + mCurrentBombRadius*Math.sin(fixAngle*i));
canvas.drawBitmap(bombShape, leftPosition, topPosition, mBombPaint);
}
}
}
/**
* 设置当前红包加载进度
*
* @param currentProgress
*/
private synchronized void setCurrentProgress(float currentProgress) {
mCurrentProgress = currentProgress;
invalidate();
}
/**
* 设置红包进度总进度
*
* @param totalProgress
*/
public void setTotalProgress(int totalProgress) {
mTotalProgress = totalProgress;
}
/**
* 提供给外部进行调用
*/
public void starAnimator() {
//只有当前进度为0或者当前进度等于总进度,并且所有动画执行完毕,才需要开始动画
if ((mCurrentProgress == 0 || mCurrentProgress == mTotalProgress)
&& mIsFinishAllAnimator) {
mIsFinishAllAnimator = false;
startAnimator(0, mTotalProgress / mProgressBlockCount);
}
}
/**
* 开始动画
*
* @param from
* @param to
*/
private void startAnimator(float from, float to) {
if (mProgressAnimator == null) {
mProgressAnimator = ValueAnimator.ofFloat(from, to);
mProgressAnimator.setDuration(800);
mProgressAnimator.setInterpolator(new LinearInterpolator());
mProgressAnimator.addUpdateListener(animation -> {
float currentProgress = (float) animation.getAnimatedValue();
setCurrentProgress(currentProgress);
});
mProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startExtendBombAnimator();
}
});
}else {
mProgressAnimator.setFloatValues(from, to);
}
mProgressAnimator.start();
}
/**
* 出现爆炸效果后,执行爆炸外扩,透明度渐变消失
*/
private void startExtendBombAnimator() {
if (mExtendBombAnimator == null){
mExtendBombAnimator = ValueAnimator.ofFloat(0, 1);
mExtendBombAnimator.setDuration(ANIMATOR_DURATION);
mExtendBombAnimator.setInterpolator(new DecelerateInterpolator());
mExtendBombAnimator.addUpdateListener(animation -> {
mCurrentBombRadiusChangeRate = (float) animation.getAnimatedValue();
invalidate();
});
mExtendBombAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//爆炸扩散效果执行完需要进行红包进度加载
if (mCurrentProgress != mTotalProgress) {
startAnimator(mCurrentProgress,
mTotalProgress / mProgressBlockCount + mCurrentProgress);
} else {
startScaleSmallAnimator();
}
}
});
}
mExtendBombAnimator.start();
}
/**
* 当前进度达到最大值,开始缩小动画
*/
private void startScaleSmallAnimator() {
if (mScaleAnimatorSet == null){
mScaleAnimatorSet = new AnimatorSet();
mScaleAnimatorSet.setDuration(ANIMATOR_DURATION);
mScaleAnimatorSet.setInterpolator(new LinearInterpolator());
mScaleSetListener = new AnimatorScaleSetListener(AnimatorScaleSetListener.SCALE_SMALL);
mScaleAnimatorSet.addListener(mScaleSetListener);
}else {
mScaleSetListener.setScaleType(AnimatorScaleSetListener.SCALE_SMALL);
}
mScaleAnimatorSet.playTogether(getObjectAnimator("scaleX", 1, 0.8f),
getObjectAnimator("scaleY", 1, 0.8f));
mScaleAnimatorSet.start();
}
private AnimatorScaleSetListener mScaleSetListener;
private class AnimatorScaleSetListener extends AnimatorListenerAdapter {
private final static int SCALE_SMALL = 0;
private final static int SCALE_BIG = 1;
private int scaleType;
public AnimatorScaleSetListener(int scaleType) {
this.scaleType = scaleType;
}
public void setScaleType(int scaleType) {
this.scaleType = scaleType;
}
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
switch (scaleType){
case SCALE_SMALL:
startScaleBigAnimator();
break;
case SCALE_BIG:
mScaleAnimatorCount--;
//如果缩放动画次数不为0,则进行缩放动画
if (mScaleAnimatorCount != 0) {
startScaleSmallAnimator();
} else {
mScaleAnimatorCount = 3;
mIsFinishAllAnimator = true;
}
break;
}
}
}
/**
* 缩小动画结束后,开始放大
*/
private void startScaleBigAnimator() {
mScaleAnimatorSet.playTogether(getObjectAnimator("scaleX", 0.8f, 1),
getObjectAnimator("scaleY", 0.8f, 1));
mScaleSetListener.setScaleType(AnimatorScaleSetListener.SCALE_BIG);
mScaleAnimatorSet.start();
}
/**
* 获取属性动画对象
* @param propertyName
* @param values
* @return
*/
private ObjectAnimator getObjectAnimator( String propertyName, float... values){
return ObjectAnimator.ofFloat(this,propertyName,values);
}
/**
* 优化处理
*
* @param visibility
*/
@Override
public void setVisibility(int visibility) {
super.setVisibility(INVISIBLE);
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
parent.removeView(this);
}
clearAnimation();
}
/**
* 是否动画监听,避免内存泄露
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.e("RedPackageView", "onDetachedFromWindow");
//移除动画监听
if (mProgressAnimator!=null){
Log.e("RedPackageView", "Remove_ProgressAnimator");
mProgressAnimator.removeAllUpdateListeners();
mProgressAnimator.removeAllListeners();
mProgressAnimator = null;
}
if (mScaleAnimatorSet !=null){
Log.e("RedPackageView", "Remove_ScaleAnimatorSet");
mScaleAnimatorSet.removeAllListeners();
mScaleAnimatorSet = null;
}
if (mExtendBombAnimator != null){
Log.e("RedPackageView", "Remove_ExtendBombAnimator");
mExtendBombAnimator.removeAllUpdateListeners();
mExtendBombAnimator.removeAllListeners();
mExtendBombAnimator = null;
}
}
}
注意:
1、绘制爆炸形状 Bitmap 时,可能资源图片切图不规范,会导致绘制 Bitmap 的中心位置,不准确。
中心位置坐标:
centerX = (float) (right - bombShape.getWidth() / 2 + mCurrentBombRadius * Math.cos(fixAngle * i));
centerY= (float) (top + progressHeight / 2 - bombShape.getHeight() / 2 + mCurrentBombRadius * Math.sin(fixAngle * i));
2、使用LottiAnimatorView 时,不用频繁进行 addLIstener()操作,不然会出现ANR 现象。
3、在 View被移除出 Window 时,需要在 onDetachedFromWindow()方法中进行动画移除,关于View的内容都需要移除,避免内存泄露