首先,来看一下今天我们需要实现的一个效果:
就是这么一个效果(最后从网上照了一张adidas的壁纸,希望不要被打)
首先,我们先来分析一下这个动画的实现步骤:
- 首先,第一个进入的是一个6个小圆组成的一个大圆的旋转动画
- 在旋转动画结束了,伴随着的是一个小圆的扩张和聚合动画
- 最后聚合到一个点之后,开启水波纹动画,过渡到呈现加载的内容
首先,我们先来实现第一个效果(小圆的旋转效果):
今天给出的实现方法是:首先通过角度和半径计算得到小圆的中心点,绘制6个小圆,然后开启旋转动画,改变旋转的角度,然后重新绘制6个小圆,就可以了!
下面直接上这一部分的代码:
/**
* Created by DELL on 2017/9/17.
* Description : 加载动画
*/
public class YahooLoadingView extends View {
//中心点X 中心点Y 对角线的一半
private float centerX,centerY,diagonal;
//小圆的颜色
private int[] mColorArray ;
//每一个圆之间的角度
private float mAngle;
//旋转的角度
private float mRotateAngle = 0;
//画小圆的画笔
private Paint mCirclePaint;
//大圆的半径为整体宽度的1/4
private float mRotateRadius;
//小圆的半径
private float mCircleRadius = 18;
private LoadingState mLoadingState;
private ValueAnimator mValueAnimator;
//加载过程中的背景色
private int mBackgroundColor;
public YahooLoadingView(Context context) {
this(context,null);
}
public YahooLoadingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public YahooLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mColorArray = context.getResources().getIntArray(R.array.splash_circle_colors);
mAngle = (float) ((Math.PI*2)/mColorArray.length);
mBackgroundColor = context.getResources().getColor(R.color.splash_bg);
//初始化画笔
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setDither(true);
mCirclePaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//初始化宽 高 对角线
centerX = w/2;
centerY = h/2;
diagonal = (float) (Math.sqrt(w*w+h*h)/2f);
mRotateRadius = w/6;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mLoadingState == null){
mLoadingState = new RotateLoadingState();
}
mLoadingState.drawState(canvas);
}
//画6个小圆 开启旋转动画
private void drawCircle(Canvas canvas){
for(int i = 0;i<mColorArray.length;i++){
float rotateAngle = mAngle*i + mRotateAngle;
float x = (float) (centerX+Math.cos(rotateAngle)*mRotateRadius);
float y = (float) (centerY+Math.sin(rotateAngle)*mRotateRadius);
mCirclePaint.setColor(mColorArray[i]);
canvas.drawCircle(x,y,mCircleRadius,mCirclePaint);
}
}
private void drawBackground(Canvas canvas){
canvas.drawColor(mBackgroundColor);
}
//策略模式 分为三种状态
public abstract class LoadingState{
public abstract void drawState(Canvas canvas);
}
//旋转动画
public class RotateLoadingState extends LoadingState{
public RotateLoadingState(){
mValueAnimator = ValueAnimator.ofFloat(0f,(float) Math.PI*2);
mValueAnimator.setDuration(5000);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRotateAngle = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.start();
}
@Override
public void drawState(Canvas canvas) {
//画背景
drawBackground(canvas);
//画小圆
drawCircle(canvas);
}
//停止动画
public void cancelAnimator(){
if(mValueAnimator != null){
mValueAnimator.cancel();
}
}
}
}
来一张实现之后的效果图:
第二步:需要实现的是一个聚合动画
这里的聚合动画分为两步,第一步是扩展动画,扩展完成时候实现收缩动画。这里扩展动画可以合并到收缩动画中一起执行,如何实现呢?自然是通过插值器了!这个插值器叫OvershootInterpolator,有一个反弹功能!
public class PolymerizationLoadingState extends LoadingState{
public PolymerizationLoadingState(){
mValueAnimator = ValueAnimator.ofFloat(mRotateRadius,0);
mValueAnimator.setDuration(1500);
mValueAnimator.setInterpolator(new OvershootInterpolator(20f));
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRotateRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
@Override
public void drawState(Canvas canvas) {
//画背景
drawBackground(canvas);
//画小圆
drawCircle(canvas);
}
}
public void setDisappear(){
if(mLoadingState != null && mLoadingState instanceof RotateLoadingState){
((RotateLoadingState) mLoadingState).cancelAnimator();
post(new Runnable() {
@Override
public void run() {
mLoadingState = new PolymerizationLoadingState();
}
});
}
}
在代码中添加了聚合动画的LoadingState(这里使用了策略设计模式,这样的画,根据其当前的状态,便会执行对应的动画)
然后,我们需要在加载动画的地方触发setDisappear()这个方法,切换当前的LoadingState:
private Handler mHandler = new Handler();
private YahooLoadingView mLoadingview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView(){
mLoadingview = (YahooLoadingView) findViewById(R.id.yahoo_loadingview);
startLoading();
}
private void startLoading(){
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mLoadingview.setDisappear();
}
},5000);
}
这里就在MainActivity中做了一个延时操作进行模拟!
好了,我们来看看现在的效果图:
下面开始做第三步,实现水波纹的扩散效果:这里使用的实现方式是:使用画笔画一个中空的圆,慢慢向周围扩散:
public class ExpandLoadingState extends LoadingState{
public ExpandLoadingState(){
mValueAnimator = ValueAnimator.ofFloat(0,diagonal);
mValueAnimator.setDuration(400);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mHoldRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
@Override
public void drawState(Canvas canvas) {
drawBackground(canvas);
}
}
然后修改drawableBackground的方法:
private void drawBackground(Canvas canvas){
if(mHoldRadius>0){
float stokenWidth = diagonal - mHoldRadius;
mBackgroundPaint.setStrokeWidth(stokenWidth);
float radius = mHoldRadius+stokenWidth/2;
canvas.drawCircle(centerX,centerY,radius,mBackgroundPaint);
}else{
canvas.drawColor(mBackgroundColor);
}
}
好了 下面 我们来看看整体的效果:
到这里,整个效果就完成了!