本篇我们来看canvas的实际应用,这里会应用到属性动画ValueAnimator类,下面先看一下具体的效果
上述效果图中主要由三部分组成,第一,绘制6个小球并旋转,第二,6个小球先扩展然后收缩,第三,水波纹效果,展示一张图片。
1)旋转部分
首先,将6个小球分别绘制到屏幕上,调用 canvas.drawCircle()方法来绘制小球,根据api的参数,这里我们需要计算出每个小球的圆心坐标,6个小球是在一个圆内,也就是说将一个圆均分为了6份,而整个圆是360°,那么两小球之间的夹角就是
float rotate = Math.PI*2/ballColor.length 这里ballColor是定义的6个小球颜色的数组int ballColor[],所有小球之间的夹角就是
//计算角度
float rotate = (float) (Math.PI * 2 / ballColor.length);
for (int i = 0; i < ballColor.length; i++) {
//计算其他小球对应的角度
float angel = rotate * i;
}
角度有了,如何计算出每个小球的圆心坐标点呢,我们以一张图来说明坐标的计算
∠ABC 就是上面我们计算出的角度值angel ,AB长是圆环的半径,由我们自己定义,那么A点的X坐标就是Math.cos(angel) *radiusCurBall(圆环半径)+centerX,相应的Y坐标就是Math.sin(angel) *radiusCurBall+centerY ,这里的centerX和centerY 表示画布的中心点坐标,这样每个小球的圆心坐标就有了。
private void drawCirlce(Canvas canvas) {
//计算角度
float rotate = (float) (Math.PI * 2 / ballColor.length);
for (int i = 0; i < ballColor.length; i++) {
//计算其他小球对应的角度
float angel = rotate * i;
float cx = (float) (Math.cos(angel) * radiusCurBall + centerX);
float cy = (float) (Math.sin(angel) * radiusCurBall + centerY);
paintSm.setColor(ballColor[i]);
canvas.drawCircle(cx, cy, radiusSmBall, paintSm);
}
}
小球绘制完毕后,该执行旋转动画了,这里采用ValueAnimator来执行,每个小球的旋转角度是从0到360°,调用valueAnimator.addUpdateListener,获取到动画在执行过程中的角度,将该角度传递到计算小球角度的逻辑代码中,从而完成小球的旋转
valueAnimator = ValueAnimator.ofFloat(0f, (float) (Math.PI * 2));
valueAnimator.setRepeatCount(2);
valueAnimator.setDuration(rotateDuration);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rotateAngel = (float) animation.getAnimatedValue();
invalidate();
}
});
Animator.start();
rotateAngel 就是获取的实时旋转角度,修改绘制小球drawCirlce()方法
private void drawCirlce(Canvas canvas) {
//计算角度
float rotate = (float) (Math.PI * 2 / ballColor.length);
for (int i = 0; i < ballColor.length; i++) {
//计算其他小球对应的角度
// float angel = rotate * i;
float angel = rotate * i + rotateAngel;
float cx = (float) (Math.cos(angel) * radiusCurBall + centerX);
float cy = (float) (Math.sin(angel) * radiusCurBall + centerY);
paintSm.setColor(ballColor[i]);
canvas.drawCircle(cx, cy, radiusSmBall, paintSm);
}
}
这样,小球就开始旋转了。
下面再来看一下第二个过程,扩展和聚合。在该动画中,我们使用到了一个新的插值器OvershootInterpolator,它表示动画在执行过程中,会向前甩一定的值,然后会回到原来位置,在效果图中,小球是先向外扩散,然后再聚合,那么动画的执行过程就是小球半径到圆环半径。
valueAnimator = ValueAnimator.ofFloat(radiusSmBall, radiusBigBall);
valueAnimator.setDuration(rotateDuration);
valueAnimator.setInterpolator(new OvershootInterpolator(5f));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radiusCurBall = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.reverse();
这里我们调用了reverse()方法,将动画反向执行,从而达到效果图中先扩散再聚合的目的。
最后再来看一下水波纹效果,实际上是不断的绘制一个空心圆,调整其strokeWidth的大小,使其达到水波纹特效,再第二步操作中,动画执行完后,当前圆环的半径就是小球的半径值,那么水波纹动画的起始值就是该圆环半径,最终值是手机斜对角线长度的一半。
这里有一点需要留意,在上一篇的文章中我们提到过canvas.drawCircle()的使用,如果设置了paint的strokeWidth值,那么圆的实际半径是内圆的半径+strokeWidth/2
valueAnimator = ValueAnimator.ofFloat(radiusSmBall, mDistane);
valueAnimator.setDuration(rotateDuration);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radiusPole = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
再来看一下绘制水波纹
float strokeWidth = mDistane - radiusPole;
float realRadius = radiusPole + strokeWidth / 2;
paintPole.setStrokeWidth(strokeWidth);
canvas.drawCircle(centerX, centerY, realRadius, paintPole);
看一下完整实现
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
public class AnimView extends View {
//6个小球半径
private float radiusSmBall = 18;
//旋转圆半径
private float radiusBigBall = 90;
//扩散圆半径
private float radiusCurBall = radiusBigBall;
//水波纹半径
private float radiusPole;
//旋转角度
private float rotateAngel;
//小球画笔
private Paint paintSm;
//水波纹画笔
private Paint paintPole;
//背景色
private int bgColor = Color.WHITE;
//6个小球颜色数组
private int ballColor[];
//中心点坐标
private float centerX;
private float centerY;
private SplashState state;
private ValueAnimator valueAnimator;
private long rotateDuration = 900;
//屏幕斜对角距离,水波纹的最大半径
private float mDistane;
public AnimView(Context context) {
this(context, null);
}
public AnimView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
//动画效果,1.6个小球转动,2.6个小球扩散、聚合,3.水波纹效果
private void init(Context context) {
ballColor = context.getResources().getIntArray(R.array.splash_circle_colors);
paintSm = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPole = new Paint();
paintPole.setStyle(Paint.Style.STROKE);
paintPole.setColor(bgColor);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (state == null) {
state = new TurnState();
}
state.drawState(canvas);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w * 1f / 2;
centerY = h * 1f / 2;
mDistane = (float) (Math.hypot(w, h)/2);
}
/**
* 旋转
*/
private class TurnState extends SplashState {
public TurnState() {
valueAnimator = ValueAnimator.ofFloat(0f, (float) (Math.PI * 2));
valueAnimator.setRepeatCount(2);
valueAnimator.setDuration(rotateDuration);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rotateAngel = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
state = new MergeState();
}
});
valueAnimator.start();
}
@Override
void drawState(Canvas canvas) {
drawBackground(canvas);
drawCirlce(canvas);
}
}
/**
* 扩散、收缩
*/
private class MergeState extends SplashState {
public MergeState() {
valueAnimator = ValueAnimator.ofFloat(radiusSmBall, radiusBigBall);
valueAnimator.setDuration(rotateDuration);
valueAnimator.setInterpolator(new OvershootInterpolator(5f));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radiusCurBall = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
state = new PoleState();
}
});
valueAnimator.reverse();
}
@Override
void drawState(Canvas canvas) {
drawBackground(canvas);
drawCirlce(canvas);
}
}
/**
* 水波纹
*/
private class PoleState extends SplashState {
public PoleState() {
valueAnimator = ValueAnimator.ofFloat(radiusSmBall, mDistane);
valueAnimator.setDuration(rotateDuration);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radiusPole = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
@Override
void drawState(Canvas canvas) {
drawBackground(canvas);
}
}
private void drawBackground(Canvas canvas) {
//表示该绘制水波纹
if (radiusPole > 0) {
float strokeWidth = mDistane - radiusPole;
float realRadius = radiusPole + strokeWidth / 2;
paintPole.setStrokeWidth(strokeWidth);
canvas.drawCircle(centerX, centerY, realRadius, paintPole);
} else {
canvas.drawColor(bgColor);
}
}
private void drawCirlce(Canvas canvas) {
//计算角度
float rotate = (float) (Math.PI * 2 / ballColor.length);
for (int i = 0; i < ballColor.length; i++) {
//计算其他小球对应的角度
// float angel = rotate * i;
float angel = rotate * i + rotateAngel;
float cx = (float) (Math.cos(angel) * radiusCurBall + centerX);
float cy = (float) (Math.sin(angel) * radiusCurBall + centerY);
paintSm.setColor(ballColor[i]);
canvas.drawCircle(cx, cy, radiusSmBall, paintSm);
}
}
abstract class SplashState {
abstract void drawState(Canvas canvas);
}
}
以及xml布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView
android:scaleType="fitXY"
android:src="@mipmap/content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.example.paintapp.AnimView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>