Canvas-实际案例操作 属性动画+水波纹效果

1,485 阅读3分钟

canvas的一些常用操作,先上图




上述动画包含三步

  1. 六个小球旋转动画
  2. 六个小球扩散聚合动画
  3. 水波纹动画


具体实现思路,还是老规矩,自定义View

public class SplashView extends View
{
    //定义旋转圆的画笔
    private Paint mPaint;
    //表示旋转圆的中心坐标
    //屏幕中心点
    private float mCenterX;
    private float mCenterY;

    //小球的背景颜色数组,每个小球的颜色可能不一样
    private int[] mCircleColors;
    //背景色
    private int mBackgroundColor = Color.WHITE;

    //6个小球的半径
    private float mCircleRadius = 18;
    //旋转大圆的半径
    private float mRotateRadius = 90;

    //当前大圆的半径
    private float mCurrentRotateRadius = mRotateRadius;

    //当前大圆的旋转角度
    private float mCurrentRotateAngle = 0F;

    //属性动画
    private ValueAnimator mValueAnimator;

    //表示旋转动画的时长
    private int mRotateDuration = 1200;

    private SplashState mState;

    //表示斜对角线长度的一半,扩散圆最大半径
    private float mDistance;

    //扩散圆的半径
    private float mCurrentHoleRadius = 0F;

    //扩散圆的画笔
    private Paint mHolePaint;

    public SplashView(Context context)
    {
        super(context);
        init(context);
    }

    public SplashView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init(context);
    }

    public SplashView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context)
    {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);

        mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHolePaint.setStyle(Paint.Style.STROKE);
        mHolePaint.setColor(mBackgroundColor);

        //从配置文件中拿到相应的小球颜色值
        mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
    }


重写onSizeChanged方法,得到View的具体大小,计算出 mCenterX,mCenter屏幕中心点的位置
mDistance水波纹圆的最大半径

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
    super.onSizeChanged(w, h, oldw, oldh);
    mCenterX = w * 1f / 2;
    mCenterY = h * 1f / 2;
    //w和h平方和的二次方根 就是利用勾股定理 求出屏幕斜对角线的长度 除以2表示水波纹空心圆的最大半径
    mDistance = (float) (Math.hypot(w, h) / 2);
}
//定义一个抽象类,各种状态的绘制
private abstract class SplashState
{
    abstract void drawState(Canvas canvas);
}

接下来我们要将六个小圆依次放到屏幕上,利用三角函数,根据具体的角度数,可以求出每一个小球的 (x,y)坐标,然后绘制小球即可




//1.小球旋转动画类
private class RotateState extends SplashState
{
    private RotateState()
    {
        mValueAnimator = ValueAnimator.ofFloat(0, (float) (Math.PI * 2));
        mValueAnimator.setRepeatCount(2);
        mValueAnimator.setDuration(mRotateDuration);
        mValueAnimator.setInterpolator(new LinearInterpolator());
        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);
                mState = new MerginState();
            }
        });
        mValueAnimator.start();
    }

    @Override
    void drawState(Canvas canvas)
    {
        //绘制背景
        drawBackground(canvas);
        //绘制小球
        drawCircles(canvas);
    }
}
//绘制小球具体的位置
private void drawCircles(Canvas canvas)
{
    float rotateAngle = (float) (Math.PI * 2 / mCircleColors.length);
    for (int i = 0; i < mCircleColors.length; i++)
    {
        // x = r * cos(a) + centX;
        // y = r * sin(a) + centY;
        //mCurrentRotateAngle 不断变化,角度也随之变化,重新计算小球的 x,y坐标
        float angle = i * rotateAngle + mCurrentRotateAngle;
        float cx = (float) (Math.cos(angle) * mCurrentRotateRadius+mCenterX);
        float cy = (float) (Math.sin(angle) * mCurrentRotateRadius+mCenterY);
        mPaint.setColor(mCircleColors[i]);
        canvas.drawCircle(cx, cy, mCircleRadius, mPaint);
    }
}

private void drawBackground(Canvas canvas)
{
    if (mCurrentHoleRadius > 0){
        //绘制空心圆
        float strokeWidth = mDistance - mCurrentHoleRadius;
        float radius = strokeWidth / 2 + mCurrentHoleRadius;
        mHolePaint.setStrokeWidth(strokeWidth);
        canvas.drawCircle(mCenterX,mCenterY, radius, mHolePaint);
    }else{
        canvas.drawColor(mBackgroundColor);
    }
}

@Override
protected void onDraw(Canvas canvas)
{
    super.onDraw(canvas);
    if (mState == null)
    {
        mState = new RotateState();
    }
    mState.drawState(canvas);
}

当第一个动画执行完成后,就接着执行小球的扩散动画

//2.扩散聚合
private class MerginState extends SplashState
{
    private MerginState()
    {
        mValueAnimator = ValueAnimator.ofFloat(mCircleRadius, mRotateRadius);
        mValueAnimator.setDuration(mRotateDuration);
        mValueAnimator.setInterpolator(new OvershootInterpolator(10f));
        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)
            {
                super.onAnimationEnd(animation);
                mState = new ExpandState();
            }
        });
        //mValueAnimator.start();
        mValueAnimator.reverse();
    }

    @Override
    void drawState(Canvas canvas)
    {
        //绘制背景
        drawBackground(canvas);
        //绘制小球
        drawCircles(canvas);
    }
}

当第二个动画执行完后,执行水波纹动画,绘制空心圆

 //3.水波纹
    private class ExpandState extends SplashState{

        public ExpandState() {
            mValueAnimator = ValueAnimator.ofFloat(mCircleRadius, mDistance);
//            mValueAnimator.setRepeatCount(2);
            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 drawBackground(Canvas canvas)
{
    if (mCurrentHoleRadius > 0){
        //绘制空心圆
        float strokeWidth = mDistance - mCurrentHoleRadius;
        float radius = strokeWidth / 2 + mCurrentHoleRadius;
        mHolePaint.setStrokeWidth(strokeWidth);
        canvas.drawCircle(mCenterX,mCenterY, radius, mHolePaint);
    }else{
        canvas.drawColor(mBackgroundColor);
    }
}

最后附上xml文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@mipmap/content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.rx.myapplication.SplashView
        android:id="@+id/splash"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>