加载动画效果

动画效果图
效果图

实现思路:
首先分析动动画的组成,细分我们会发现,动画主要有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;
    }
}

完整源码链接https://github.com/coolfuwei/LoadAnim

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值