动画效果图
效果图
实现思路:
首先分析动动画的组成,细分我们会发现,动画主要有3个部分组成,第一个是小球第旋转动画,一个是小球的集合动画,还一个是水波纹扩散动画,3个动画都执行完了之后显示加载完成的页面,这里使用一张图片来代替,其实就是一个view
首先旋转动画
旋转动画由6个小球旋转形成,我们可以使用属性动画来实现,分别计算出每一个小球的位置,并不断的改变其绘制角度,下面是源码
/**
* 旋转动画
*/
public class RotateAnimStatus extends AnimStatus {
ValueAnimator valueAnimator;
public RotateAnimStatus() {
valueAnimator = ValueAnimator.ofFloat(0f, (float) Math.PI * 2);
valueAnimator.setDuration(mRotateAnimDuration);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentRotateDegree = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
@Override
public void onDraw(Canvas canvas) {
drawBraground(canvas);//画背景
drawCricle(canvas);//画小圆
}
private void calcelAnim() {
valueAnimator.cancel();
}
}
这里的mCurrentRotateDegree是0-2 兀的弧度制,为了后面方便使用三角函数求坐标,其中drawBraground和drawCricle方法如下:
/**
* 画圆
*
* @param canvas
*/
private void drawCricle(Canvas canvas) {
int smallCircleCount = mSmallCircleColors.length;
float preDegree = (float) (Math.PI * 2 / smallCircleCount);
for (int i = 0; i < smallCircleCount; i++) {
int cx = (int) (mPathCircleRadius * Math.cos(preDegree * i + mCurrentRotateDegree)) + mCenterX;
int cy = (int) (mPathCircleRadius * Math.sin(preDegree * i + mCurrentRotateDegree) + mCenterY);
mSmallCirclePaint.setColor(mSmallCircleColors[i]);
canvas.drawCircle(cx, cy, mSmallCircleRadius, mSmallCirclePaint);
}
}
/**
* 画背景
*
* @param canvas
*/
private void drawBraground(Canvas canvas) {
if(mCurrentExpandRadius == 0) {
canvas.drawColor(Color.WHITE);
}else {
//得到画笔的宽度 = 对角线/2 - 空心圆的半径
float strokeWidth = mExpandStrokeWith - mCurrentExpandRadius;
mBackGroundPaint.setStrokeWidth(strokeWidth);
//画圆的半径 = 空心圆的半径 + 画笔的宽度/2
float radius = mCurrentExpandRadius + strokeWidth/2;
canvas.drawCircle(mCenterX,mCenterY,radius,mBackGroundPaint);
}
}
drawCricle方法中,mSmallCircleColors是颜色数组,包含6个颜色,mPathCircleRadius是小圆圆心运动的轨迹圆半径,cx,cy是小球圆心坐标,mCenterX,mCenterY是屏幕中点坐标,cx的计算方式为(mPathCircleRadius * Math.cos(preDegree * i + mCurrentRotateDegree)) + mCenterX,其实就是一个简单的三角函数计算
drawBraground方法中,此时mCurrentExpandRadius为0,那么就是画一个白色的背景,当不为零时也就是用来画扩散动画,这里留着后面讲。
旋转动画是在onDraw方法中开启的,并且是一个无限循环的动画,我们需要一个时机将它取消,并进入下一个聚合动画
@Override
protected void onDraw(Canvas canvas) {
if (mAnimStatus == null) {
mAnimStatus = new RotateAnimStatus();
}
mAnimStatus.onDraw(canvas);
}
onDismissAnim这个方法是外部调用的,我这里是在activity中3s后调用,启动聚合动画
/**
* 3s后准备消失动画,小圆向内聚合
*/
public void onDismissAnim() {
if (mAnimStatus != null && mAnimStatus instanceof RotateAnimStatus) {
RotateAnimStatus rotateAnimStatus = (RotateAnimStatus) mAnimStatus;
rotateAnimStatus.calcelAnim();
}
mAnimStatus = new MergingAnimStatus();
invalidate();
}
下面来看第二个聚合动画
聚合动画就是小球圆心运动的圆的半径不断的变为0的过程
/**
* 聚合动画
*/
public class MergingAnimStatus extends AnimStatus{
public MergingAnimStatus() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mPathCircleRadius,0);
valueAnimator.setDuration(mMergingAnimDuration);
valueAnimator.setInterpolator(new OvershootInterpolator(10));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPathCircleRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimStatus = new ExpandAnim();
invalidate();
}
});
valueAnimator.start();
}
在聚合动画结束后开始进行第三个动画也就是扩散动画,源码如下:
/**
* 扩散动画
*/
public class ExpandAnim extends AnimStatus{
public ExpandAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mSmallCircleRadius,mExpandCirclerRadius);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentExpandRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(mMergingAnimDuration/2);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if(onAnimEndListener != null){
onAnimEndListener.onExpandAnimStart();
}
}
});
valueAnimator.start();
}
@Override
public void onDraw(Canvas canvas) {
drawBraground(canvas);
}
}
mSmallCircleRadius 为小圆的半径,mExpandCirclerRadius是对角线的一半
这里的onDraw会调用drawBraground,再回顾一下这个方法
/**
* 画背景
*
* @param canvas
*/
private void drawBraground(Canvas canvas) {
if(mCurrentExpandRadius == 0) {
canvas.drawColor(Color.WHITE);
}else {
//得到画笔的宽度 = 对角线/2 - 空心圆的半径
float strokeWidth = mExpandStrokeWith - mCurrentExpandRadius;
mBackGroundPaint.setStrokeWidth(strokeWidth);
//画圆的半径 = 空心圆的半径 + 画笔的宽度/2
float radius = mCurrentExpandRadius + strokeWidth/2;
canvas.drawCircle(mCenterX,mCenterY,radius,mBackGroundPaint);
}
}
此时mCurrentExpandRadius不断的变化,并且大于0,所以就进入else里,我们主要看d ra wCircle的半径和strokeWidth是怎样计算的,strokeWidth为对角线的一半减去空心圆的半径(mCurrentExpandRadius),而radius即是空心圆的半径加上strokeWidth的一半,到这里算是完成了
完整代码如下:
package com.cool.loadanim;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
/**
* Created by cool on 2017/6/15.
*/
public class SplashView extends View {
private Paint mSmallCirclePaint;//6个小圆的画笔
private Paint mBackGroundPaint;//背景画笔
private int[] mSmallCircleColors;//6个小圆的颜色
private float mPathCircleRadius;//小圆运动轨迹圆
private float mSmallCircleRadius;//小圆半径
private float mExpandCirclerRadius;//扩散圆半径
private int mCenterX;//屏幕x中点
private int mCenterY;//屏幕y中点
private int mRotateAnimDuration = 1200;//旋转动画执行时间
private int mMergingAnimDuration = 1200;//聚合动画执行时间
private float mCurrentRotateDegree;
private float mCurrentExpandRadius = 0;//当前扩散半径
private float mExpandStrokeWith;
private AnimStatus mAnimStatus;
public SplashView(Context context) {
this(context, null);
}
public SplashView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SplashView);
mPathCircleRadius = ta.getDimension(R.styleable.SplashView_PathCircleRadius, dp2px(context, 80));
mSmallCircleRadius = ta.getDimension(R.styleable.SplashView_SmallCircleRadius, dp2px(context, 10));
ta.recycle();
init(context);
}
private void init(Context context) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mSmallCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
mSmallCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackGroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackGroundPaint.setStyle(Paint.Style.STROKE);
mBackGroundPaint.setColor(Color.WHITE);
}
/**
* 3s后准备消失动画,分为2个,小圆向内集合和水波纹扩散动画
*/
public void onDismissAnim() {
if (mAnimStatus != null && mAnimStatus instanceof RotateAnimStatus) {
RotateAnimStatus rotateAnimStatus = (RotateAnimStatus) mAnimStatus;
rotateAnimStatus.calcelAnim();
}
mAnimStatus = new MergingAnimStatus();
invalidate();
}
public abstract class AnimStatus {
public abstract void onDraw(Canvas canvas);
}
@Override
protected void onDraw(Canvas canvas) {
if (mAnimStatus == null) {
mAnimStatus = new RotateAnimStatus();
}
mAnimStatus.onDraw(canvas);
}
/**
* 旋转动画
*/
public class RotateAnimStatus extends AnimStatus {
ValueAnimator valueAnimator;
public RotateAnimStatus() {
valueAnimator = ValueAnimator.ofFloat(0f, (float) Math.PI * 2);
valueAnimator.setDuration(mRotateAnimDuration);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentRotateDegree = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
@Override
public void onDraw(Canvas canvas) {
drawBraground(canvas);//画背景
drawCricle(canvas);//画小圆
}
private void calcelAnim() {
valueAnimator.cancel();
}
}
/**
* 聚合动画
*/
public class MergingAnimStatus extends AnimStatus{
public MergingAnimStatus() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mPathCircleRadius,0);
valueAnimator.setDuration(mMergingAnimDuration);
valueAnimator.setInterpolator(new OvershootInterpolator(10));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPathCircleRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimStatus = new ExpandAnim();
invalidate();
}
});
valueAnimator.start();
}
@Override
public void onDraw(Canvas canvas) {
drawBraground(canvas);//画背景
drawCricle(canvas);//画小圆
}
}
/**
* 扩散动画
*/
public class ExpandAnim extends AnimStatus{
public ExpandAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mSmallCircleRadius,mExpandCirclerRadius);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentExpandRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(mMergingAnimDuration/2);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if(onAnimEndListener != null){
onAnimEndListener.onExpandAnimStart();
}
}
});
valueAnimator.start();
}
@Override
public void onDraw(Canvas canvas) {
drawBraground(canvas);
}
}
/**
* 画圆
*
* @param canvas
*/
private void drawCricle(Canvas canvas) {
int smallCircleCount = mSmallCircleColors.length;
float preDegree = (float) (Math.PI * 2 / smallCircleCount);
for (int i = 0; i < smallCircleCount; i++) {
int cx = (int) (mPathCircleRadius * Math.cos(preDegree * i + mCurrentRotateDegree)) + mCenterX;
int cy = (int) (mPathCircleRadius * Math.sin(preDegree * i + mCurrentRotateDegree) + mCenterY);
mSmallCirclePaint.setColor(mSmallCircleColors[i]);
canvas.drawCircle(cx, cy, mSmallCircleRadius, mSmallCirclePaint);
}
}
/**
* 画背景
*
* @param canvas
*/
private void drawBraground(Canvas canvas) {
if(mCurrentExpandRadius == 0) {
canvas.drawColor(Color.WHITE);
}else {
//得到画笔的宽度 = 对角线/2 - 空心圆的半径
float strokeWidth = mExpandStrokeWith - mCurrentExpandRadius;
mBackGroundPaint.setStrokeWidth(strokeWidth);
//画圆的半径 = 空心圆的半径 + 画笔的宽度/2
float radius = mCurrentExpandRadius + strokeWidth/2;
canvas.drawCircle(mCenterX,mCenterY,radius,mBackGroundPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mExpandCirclerRadius = (float) Math.sqrt(mCenterX*mCenterX + mCenterY*mCenterY);
mExpandStrokeWith = mExpandCirclerRadius;
}
private OnExpandAnimStartListener onAnimEndListener;
public void setOnExpandAnimStartListener(OnExpandAnimStartListener listener){
this.onAnimEndListener = listener;
}
public interface OnExpandAnimStartListener {
void onExpandAnimStart();
}
public int dp2px(Context context, int dp) {
float density = context.getResources().getDisplayMetrics().density;
int px = (int) (dp * density + 0.5f);
return px;
}
}