Android Canvas进阶之自定义View实现Splash的旋转、扩散聚合、水波纹特效

先上效果图

 动画可以分割为3阶段,第一阶段是6个小圆的旋转,第二阶段是6个小圆的扩散和收缩,第三部分是水波纹特效,动画的实现也是按照这三个阶段进行实现的。

1.初始化

自定义FlashView继承View,然后在构造方法中对画笔初始化,初始化2支画笔,一个是画小圆的,另一个是第三阶段画水波纹的

    private void init() {
        setBackgroundResource(R.drawable.girl);//先设置一张背景,模拟splash后面的界面

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHolePaint.setStyle(Paint.Style.STROKE);
        mHolePaint.setColor(Color.WHITE);//扩散圆的颜色为白色
        mSplashState = new RotateState();
    }

有一堆成员变量的声明,每个都有详细注释,一看就懂

   private Paint mPaint;//画小圆的画笔
    private Paint mHolePaint;//画水波纹的画笔
    private int[] circleColors = {0xFFFF9600, 0xFF02D1AC, 0xFFFFD200, 0xFF00C6FF, 0xFF00E099, 0xFFFF3892};//6个小圆的颜色的数组
    private float mCenterX;//旋转圆的中心横坐标
    private float mCenterY;//旋转圆的中心纵坐标
    private float mDistance;//表示斜对角线长度的一半,扩散圆最大半径
    private float mCircleRadius = 18; //6个小球的半径
    private float mRotateRadius = 90;//旋转大圆的半径
    private float mCurrentRotateRadius = mRotateRadius;//当前旋转圆的半径
    private float mCurrentRotateAngle = 0F; //当前旋转圆的旋转角度
    private float mCurrentHoleRadius = 0F;//扩散圆的半径
    private int mRotateDuration = 1000; //旋转动画的时长
    private ValueAnimator mValueAnimator;//属性动画
    private SplashState mSplashState; 

旋转圆的中心坐标和水波纹扩散圆的最大半径需要根据屏幕尺寸初始化,因此放到onMeasure中

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mCenterX = MeasureSpec.getSize(widthMeasureSpec) / 2;
        mCenterY = MeasureSpec.getSize(heightMeasureSpec) / 2;
        mDistance = (float) Math.hypot(mCenterX, mCenterY); //hypot函数是取平方根
    }

2.绘制小圆

到这里初始化工作已经做完了。在三个动画阶段开始之前先来绘制6个小球,动画的事往后稍一稍。首先定义一个抽象类SplashState,包含一个抽象方法drawState。后面的三种动画都继承自SplashState实现drawState方法,然后我们只需要在ondraw方法中调用SplashState的drawStatue方法即可,具体的实现在每个状态的drawState方法中

    private abstract class SplashState {
        abstract void drawState(Canvas canvas);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mSplashState.drawState(canvas);

    }

绘制小圆在第一个阶段旋转动画中实现,么得办法,总得找个地方去绘制,第一个是绘制小圆的背景,纯白色,第二个是绘制6个小圆,绘制背景很简单,直接绘制纯色就好,小圆的绘制稍微有一点点绕,小圆的颜色直接从定义的颜色数组里面拿,每个小圆的横坐标等于旋转的大圆半径加上旋转的大圆半径在每个小圆在极坐标中的角度的cos值,以第二个小球为例,画图可能更清晰点

    private class RotateState extends SplashState {

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
            drawCircles(canvas);//画小圆
        }
    }

    /**
     * 画背景
     */
    private void drawBackground(Canvas canvas) {
        canvas.drawColor(Color.WHITE);//绘制白色背景
    }

    /**
     * 画小圆
     */
    private void drawCircles(Canvas canvas) {
        for (int i = 0; i < circleColors.length; i++) {
            float rotate = (float) (i * Math.PI * 2 / circleColors.length) + mCurrentRotateAngle;
            mPaint.setColor(circleColors[i]);
            float cX = (float) (mCenterX + (mCurrentRotateRadius * Math.cos(rotate)));
            float cY = (float) (mCenterY + (mCurrentRotateRadius * Math.sin(rotate)));
            canvas.drawCircle(cX, cY, mCircleRadius, mPaint);
        }
    }

到这里6个小圆已经绘制出来了。

3.旋转动画

接下来就是让小球球动起来,动起来就需要用到动画了,在这里使用属性动画,属性动画定义在RotateState的构造方法中,因此我们在一开始的init方法中创建对象的时候便开始的动画。属性动画数值从0变化到2*π,就是整整一圈,然后再属性动画的值变化的时候改变当前旋转角度,然后调用invalidate方法,invalidate方法会触发ondraw方法,ondraw方法中又会调用到RotateState的drawstate方法,从而实现了旋转动画,当然还要监听旋转动画执行完毕,执行完毕后我们去启动第二阶段的动画代码比较简单,直接上

    private class RotateState extends SplashState {

        private RotateState() {
            mValueAnimator = ValueAnimator.ofFloat(0, (float) Math.PI * 2);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.setDuration(mRotateDuration);
            mValueAnimator.setRepeatCount(1);
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRotateAngle = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });

            mValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mSplashState = new MerginState();
                }
            });

            mValueAnimator.start();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
            drawCircles(canvas);//画小圆
        }
    }

4.扩散聚合动画

我们监听了第一阶段动画的结束,结束后new了一个MerginState,并赋值给mSplashState,从现在开始ondraw调用的drawState将变为MerginState的,扩散聚合动画我们是反正来的,动画的start是正向播放动画,reverse则是反正播放动画,所以我们要实现的是把旋转圆的半径从0扩散到大于旋转时候的半径值,然后再缩小到旋转时的半径值,这就要使用到OvershootInterpolator差值器了,这个差值器是开始的时候向后然后向前甩一定值后返回最后的值,关于差值器,可以参考https://blog.csdn.net/harvic880925/article/details/40049763#commentBox,按照反着来外加这个差值器,便实现了我们的效果,当然还是要监听下动画播放完毕进入第三阶段的水波纹,整个扩散聚合代码如下

    private class MerginState extends SplashState {
        private MerginState() {
            mValueAnimator = ValueAnimator.ofFloat(0, mRotateRadius);
            mValueAnimator.setDuration(mRotateDuration);
            mValueAnimator.setRepeatCount(0);
            mValueAnimator.setInterpolator(new OvershootInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRotateRadius = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mSplashState = new ExpandState();
                }
            });
            mValueAnimator.reverse();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
            drawCircles(canvas);//画小圆
        }
    }

5.水波纹动画

水波纹动画的实现思路也不难,就是画一个圆,画之前将画笔设置为空心圆(mHolePaint.setStyle(Paint.Style.STROKE)),然后我们不停地改变圆环的粗细,实现水波纹的效果,空心圆的半径等于mDistance,即这个圆正好可以把整个屏幕扩住,然后一开始圆环的宽度等于圆的半径的2倍,这样整个空心圆已经把屏幕填满,然后动态的减小圆环的宽度到0,这样看起来的效果刚好就是我们要的,在这之前我们应该改变一个drawBackground方法,前两个阶段我们绘制的是整个屏幕白色,现在只是绘制圆环为白色

    private void drawBackground(Canvas canvas) {
        if (mCurrentHoleRadius > 0) {
            mHolePaint.setStrokeWidth(2 * (mDistance - mCurrentHoleRadius));
            canvas.drawCircle(mCenterX, mCenterY, mDistance, mHolePaint);
        } else {
            canvas.drawColor(Color.WHITE);//绘制白色背景
        }
    }


    private class ExpandState extends SplashState {
        private ExpandState() {
            mValueAnimator = ValueAnimator.ofFloat(0, mDistance);
            mValueAnimator.setDuration(mRotateDuration);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentHoleRadius = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.start();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
        }
    }

整个动画的过程就算结束了,完整的代码

public class SplashView extends View {

    private Paint mPaint;//画小圆的画笔
    private Paint mHolePaint;//画水波纹的画笔
    private int[] circleColors = {0xFFFF9600, 0xFF02D1AC, 0xFFFFD200, 0xFF00C6FF, 0xFF00E099, 0xFFFF3892};//6个小圆的颜色的数组
    private float mCenterX;//旋转圆的中心横坐标
    private float mCenterY;//旋转圆的中心纵坐标
    private float mDistance;//表示斜对角线长度的一半,扩散圆最大半径
    private float mCircleRadius = 18; //6个小球的半径
    private float mRotateRadius = 90;//旋转大圆的半径
    private float mCurrentRotateRadius = mRotateRadius;//当前旋转圆的半径
    private float mCurrentRotateAngle = 0F; //当前旋转圆的旋转角度
    private float mCurrentHoleRadius = 0F;//扩散圆的半径
    private int mRotateDuration = 1000; //旋转动画的时长
    private ValueAnimator mValueAnimator;//属性动画
    private SplashState mSplashState;

    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);
        init();
    }

    private void init() {
        setBackgroundResource(R.drawable.girl);//先设置一张背景,模拟splash后面的界面

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHolePaint.setStyle(Paint.Style.STROKE);
        mHolePaint.setColor(Color.WHITE);//扩散圆的颜色为白色
        mSplashState = new RotateState();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mCenterX = MeasureSpec.getSize(widthMeasureSpec) / 2;
        mCenterY = MeasureSpec.getSize(heightMeasureSpec) / 2;
        mDistance = (float) Math.hypot(mCenterX, mCenterY); //hypot函数是取平方根
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mSplashState.drawState(canvas);

    }

    /**
     * 抽象类,三种状态继承自该类,实现drawState
     */
    private abstract class SplashState {
        abstract void drawState(Canvas canvas);
    }

    /**
     * 1.旋转动画
     */
    private class RotateState extends SplashState {

        private RotateState() {
            mValueAnimator = ValueAnimator.ofFloat(0, (float) Math.PI * 2);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.setDuration(mRotateDuration);
            mValueAnimator.setRepeatCount(1);
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRotateAngle = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });

            mValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mSplashState = new MerginState();
                }
            });

            mValueAnimator.start();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
            drawCircles(canvas);//画小圆
        }
    }


    /**
     * 2.扩散后缩放动画
     */
    private class MerginState extends SplashState {
        private MerginState() {
            mValueAnimator = ValueAnimator.ofFloat(0, mRotateRadius);
            mValueAnimator.setDuration(mRotateDuration);
            mValueAnimator.setRepeatCount(0);
            mValueAnimator.setInterpolator(new OvershootInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRotateRadius = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mSplashState = new ExpandState();
                }
            });
            mValueAnimator.reverse();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
            drawCircles(canvas);//画小圆
        }
    }

    /**
     * 3.水波纹动画
     */
    private class ExpandState extends SplashState {
        private ExpandState() {
            mValueAnimator = ValueAnimator.ofFloat(0, mDistance);
            mValueAnimator.setDuration(mRotateDuration);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentHoleRadius = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.start();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);//画背景
        }
    }

    /**
     * 画小圆
     */
    private void drawCircles(Canvas canvas) {
        for (int i = 0; i < circleColors.length; i++) {
            float rotate = (float) (i * Math.PI * 2 / circleColors.length) + mCurrentRotateAngle;
            mPaint.setColor(circleColors[i]);
            float cX = (float) (mCenterX + (mCurrentRotateRadius * Math.cos(rotate)));
            float cY = (float) (mCenterY + (mCurrentRotateRadius * Math.sin(rotate)));
            canvas.drawCircle(cX, cY, mCircleRadius, mPaint);
        }
    }

    /**
     * 画背景
     */
    private void drawBackground(Canvas canvas) {
        if (mCurrentHoleRadius > 0) {
            mHolePaint.setStrokeWidth(2 * (mDistance - mCurrentHoleRadius));
            canvas.drawCircle(mCenterX, mCenterY, mDistance, mHolePaint);
        } else {
            canvas.drawColor(Color.WHITE);//绘制白色背景
        }
    }

}

附上Demo:https://github.com/987570437/PaintDemo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值