Android属性动画2-----自定义属性动画

在《Android属性动画1-----属性动画的简单使用》中,简单介绍了属性动画的基本使用,在本章节中,将会结合前面的基础知识,实现一个自定义属性动画。
基本的特效:旋转 + 逃逸聚合 + 扩散 + 策略设计模式,完整源码如下:

public class SplashView extends View { //动画 private ValueAnimator valueAnimator; //绘制圆的画笔 private Paint mPaint = new Paint(); //绘制背景的画笔 private Paint mPaintBackground = new Paint(); //当前大圆旋转角度(弧度) private float mCurrentRotationAngle = 0F; // 大圆(里面包含很多小圆的)的半径 private float mRotationRadius = 90; // 每一个小圆的半径 private float mCircleRadius = 18; //当前大圆的半径 private float mCurrentRotationRadius = mRotationRadius; //颜色的集合 private int[] mCircleColors; //圆圈旋转的时间 private int mRotationDuration = 1200; // 屏幕正中心点坐标 private float mCenterX; private float mCenterY; //屏幕对角线的一般 private int DisBank; //空心圆初始半径 private float mHoleRadius = 0F; public SplashView(Context context) { this(context,null,0); }public SplashView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); }public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); }private void init() { //抗锯齿 mPaint.setAntiAlias(true); mPaintBackground.setAntiAlias(true); //描边 mPaintBackground.setStyle(Paint.Style.STROKE); mPaintBackground.setColor(Color.DKGRAY); //颜色集合 mCircleColors = new int[]{R.color.colorBlue,R.color.colorOrange,R.color.colorGreen, R.color.colorRed,R.color.colorYellow,R.color.colorPupple}; }//初始状态 SplashState state = null; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(state == null){ //初始状态 旋转 state = new RotateState(); } //画圆 state.drawState(canvas); }//状态转变 public void splashDisappear(){ if(state != null && state instanceof RotateState){ //动画取消 state.cancel(); post(new Runnable() { @Override public void run() { //开始聚合动画 state = new MergingState(); } }); } }//业务逻辑状态 //1.刚进来时,执行旋转动画 private class RotateState extends SplashState{ public RotateState(){ //2π == 360度 valueAnimator = ValueAnimator.ofFloat(0, (float) (Math.PI*2)); //设置匀速插值器 valueAnimator.setInterpolator(new LinearInterpolator()); //设置监听 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //当前圆的角度 mCurrentRotationAngle = (float) animation.getAnimatedValue(); //重新绘制 postInvalidate(); } }); valueAnimator.setDuration(mRotationDuration); valueAnimator.setRepeatCount(ValueAnimator.INFINITE); valueAnimator.start(); } @Override public void drawState(Canvas canvas) { //画背景 drawBackground(canvas); //画圆 drawCircle(canvas); } }@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mCenterX = w/2; mCenterY = h/2; //屏幕对角线的一半 DisBank = (int) (Math.sqrt(w*w + h*h)/2f); }private void drawCircle(Canvas canvas) { //每个圆对应的角度(360/6 = 60°) float rotationAngle = (float) (Math.PI * 2 / mCircleColors.length); //关于每个点的坐标 for (int i = 0; i < mCircleColors.length; i++) { //角度是动态变化的 float angle = i*rotationAngle + mCurrentRotationAngle; float cx = (float) (mCenterX + mCurrentRotationRadius * Math.cos(angle)); float cy = (float) (mCenterY + mCurrentRotationRadius * Math.sin(angle)); //开始画 mPaint.setColor(mCircleColors[i]); canvas.drawCircle(cx,cy,mCircleRadius,mPaint); } }private void drawBackground(Canvas canvas) { if(mHoleRadius >0){ //画圆 float strokWidth = DisBank - mHoleRadius; mPaintBackground.setStrokeWidth(strokWidth); float radius = mHoleRadius + strokWidth/2; canvas.drawCircle(mCenterX,mCenterY,radius,mPaintBackground); }else { canvas.drawColor(Color.WHITE); } }//2.数据加载完毕之后,聚合逃逸动画 private class MergingState extends SplashState{ public MergingState(){ valueAnimator = ValueAnimator.ofFloat(mRotationRadius,0); valueAnimator.setDuration(mRotationDuration); valueAnimator.setInterpolator(new OvershootInterpolator(10)); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentRotationRadius = (float) animation.getAnimatedValue(); invalidate(); } }); //监听动画完成 valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); //启动扩散动画 state = new ExpandState(); } }); valueAnimator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); drawCircle(canvas); } } //3.扩散动画以屏幕到中心线的对角线为半径 private class ExpandState extends SplashState{ publicExpandState(){ valueAnimator = ValueAnimator.ofFloat(mCircleRadius,DisBank); valueAnimator.setDuration(mRotationDuration); //valueAnimator.setInterpolator(new AccelerateInterpolator(5)); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mHoleRadius = (float) animation.getAnimatedValue(); invalidate(); } }); valueAnimator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); } }//策略模式 private abstract class SplashState{ //画动画状态 public abstract void drawState(Canvas canvas); //取消动画 public void cancel(){ valueAnimator.cancel(); } }}

下面就详细介绍动画的实现。
1、旋转动画
该动画主要分为3种:一开始是旋转动画,然后是聚合逃逸动画,最后是一个扩散动画,3种动画为3种状态,因此使用策略模式,实现3种状态。
策略模式的优点:
(1)如果在使用上述3种状态时,在判断某种状态时往往会使用if-else语句,也就是用户不选择A那么就选择B这样的一种情况。这种情况耦合性太高了,而且代码臃肿,有了策略模式我们就可以避免这种现象,
(2)策略模式遵循开闭原则,实现代码的解耦合。扩展新的方法时也比较方便,只需要继承策略基类就好了。
private abstract class SplashState{ //画动画 public abstract void drawState(Canvas canvas); //取消动画 public void cancel(){ animator.cancel(); } }//3种状态的实现 //旋转动画 public class RotateState extends SplashState{@Override public void drawState(Canvas canvas) {} }//聚合逃逸动画 public class MergingState extends SplashState{@Override public void drawState(Canvas canvas) {} }//扩散动画 public class ExpandState extends SplashState{@Override public void drawState(Canvas canvas) {} }

完成了3种状态的实现之后,默认打开的第一个动画就是旋转动画,因此在onDraw方法中,就需要先画出旋转动画。
SplashState mState = null; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(mState == null){ mState = new RotateState(); } //开始画动画 mState.drawState(canvas); }

接下来就需要处理RotateState旋转类动画中的逻辑。
//旋转动画 public class RotateState extends SplashState{ public RotateState(){ //360度旋转 animator = ValueAnimator.ofFloat(0, (float) (Math.PI * 2)); //匀速旋转 animator.setInterpolator(new LinearInterpolator()); //设置监听 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //获取当前大圆旋转的角度 mCurrentRotationAngle = (float) animation.getAnimatedValue(); postInvalidate(); } }); //设置动画的时间 animator.setDuration(mRotationDuration); //开启动画 animator.start(); } @Override public void drawState(Canvas canvas) { //画背景 drawBackground(canvas); //画圆圈 drawCircle(canvas); } }private void drawCircle(Canvas canvas) { //360度,每个小圆平均分得的度数 float mCircleAngle = (float) (Math.PI * 2 / mCircleColors.length); for (int i = 0; i < mCircleColors.length; i++) { //当前每个圆对应的角度 float mCurrentCircleAngle = i * mCircleAngle + mCurrentRotationAngle; float cx = (float) (mCenterX + mRotationRadius * Math.cos(mCurrentCircleAngle)); float cy = (float) (mCenterY + mRotationRadius * Math.sin(mCurrentCircleAngle)); //开始画圆 mPaint.setColor(mCircleColors[i]); canvas.drawCircle(cx,cy,mCircleRadius,mPaint); } }

Android属性动画2-----自定义属性动画
文章图片

在旋转动画完成之后,需要切换状态,转变为聚合逃逸动画
public void splashDisappear(){ if(mState != null && mState instanceof RotateState){ //结束当前的动画 mState.cancel(); post(new Runnable() { @Override public void run() { mState = new MergingState(); } }); } }

2、聚合逃逸动画
【Android属性动画2-----自定义属性动画】聚合逃逸动画,在旋转动画完成之后,会以中心为基准,全部的圆向圆心内聚集,因此在处理这一部分逻辑时,需要动态改变大圆的半径即可。
//聚合逃逸动画 public class MergingState extends SplashState{ public MergingState(){ //从大圆的半径---递减到0 animator = ValueAnimator.ofFloat(mRotationRadius,0); //添加张力插值器 animator.setInterpolator(new OvershootInterpolator(10)); //添加监听 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentCircleRadius = (float) animation.getAnimatedValue(); invalidate(); } }); animator.setDuration(mRotationDuration); animator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); drawCircle(canvas); } }

3、扩散动画
聚合动画的执行需要监听是否完成,如果已经完成了,那么就开始扩散动画。
//监听动画是否完成 animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); //启动扩散动画 mState = new ExpandState(); } });

当监听到聚合动画已经完成,就开启扩散动画,接下来需要处理扩散动画的逻辑。
扩散动画,是以屏幕对角线的一半为半径,然后从小圆半径开始,逐渐扩散。
//扩散动画 public class ExpandState extends SplashState{ public ExpandState(){ //设置动画 最小半径---屏幕对角线的一半 animator = ValueAnimator.ofFloat(mCircleRadius,mDiaLine); //设置监听 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mHoleRedis = (float) animation.getAnimatedValue(); invalidate(); } }); animator.setDuration(mRotationDuration); //开启动画 animator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); } }

private void drawBackground(Canvas canvas) { if(mHoleRedis >0) { //画圆 float strokeWidth = mDiaLine - mCircleRadius; mBackgroundPaint.setStrokeWidth(strokeWidth); //空心圆的半径 float redis = mHoleRedis + strokeWidth/2; canvas.drawCircle(mCenterX,mCenterY,redis,mBackgroundPaint); }else { canvas.drawColor(getResources().getColor(R.color.colorBlue)); } }

通过以上3步骤,配合ValueAnimator实现了整体的动画设计,在实际的项目中,大部分的动画实现都是以上套路,只是其中的细节可能涉及估值器和插值器的使用,下一节中,将会手写一个动画加载框架。

    推荐阅读