效果:
整个效果分为旋转、扩散聚合、水波纹效果,首先在定义好一些变量后,要先定义一个抽象类SplashState,提供抽象方法drawState供子类实现。
/**
* 这个抽象类,对外提供drawState方法,供子类实现
*/
private abstract class SplashState{
abstract void drawState(Canvas canvas);
}
要实现动画,RotateState类负责旋转,在重写的drawState方法中,首先要通过drawBackground方法来绘制背景,然后再由drawCircles方法绘制出6个小球。在6个小球的绘制过程中,首先要计算出每个小球的坐标位置,计算方式为:半径cos值+圆心x坐标,半径sin值+圆心y坐标。之后设置画笔颜色、绘制小球。
在绘制小球时,要定义SplashState mState,并需要再onDraw中判断一下,保证对象不为null,并调用其内部方法来绘制背景和绘制6个小球。
此时6个小球的静态图已经绘制完成了,接着还需要让它们动起来。换句话说,也就是要对“角度”下手。角度不断变换,重新调用onDraw方法,就能实现动态效果。因此,还需要在RotateState类中执行属性动画。在设置一系列属性动画的设置后,对其设计监听,并在回调中实时获取到动画旋转的角度mCurrentRotateAngle,并且需要将这个“实时角度”给到小球绘制的角度计算:
//每个小球的坐标:半径*cos值+圆心x坐标,半径*sin值+圆心y坐标
float angle = i*rotateAngle+mCurrentRotateAngle;//角度
由于动画要执行两次,所有还要监听动画的执行状态:
//监听动画的执行状态
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
当第一种动画执行完后,要切换到第二种动画(扩散聚合)。
扩散聚合:
在第一个动画的状态监听回调中,切换成MerginState,执行扩散聚合动画。OvershootInterpolator插值器能实现动画的反向操作效果
首先要绘制背景,然后实现6个小球的扩散聚合效果。这个效果,其实是归功于mRotateRadius的改变。所以要想实现这个效果,想办法改变mRotateRadius就可以了,具体做法还是要借助属性动画。
//扩散的执行半径,从小圆的半径mCircleRadius开始,到大圆mRotateRadius结束
mValueAnimator=ValueAnimator.ofFloat(mCircleRadius,mRotateRadius);
当然了,还需要设置一些属性。其中最关键的就是监听函数中,获取到旋转圆的半径后,需要将其更新为小球的半径。
接下来,就剩下第三个水波纹效果的动画了。实际上就是借助属性动画画一个圆。扩散半径是从mCircleRadius到mDistance,插值器仍旧选择线性插值器。在动画的监听函数中,需要动态修改mCurrentHoleRadius,并调用invalidate()方法来使onDraw方法被触发调用。
别忘了还要在drawBackground方法中做判断,当前动画是否是第三种动画。如果是第三种动画,要绘制一个空心圆:
//绘制一个空心圆
float strokeWidth=mDistance-mCurrentHoleRadius;
float radius=strokeWidth/2+mCurrentHoleRadius; //真实半径
mHolePaint.setStrokeWidth(strokeWidth);
canvas.drawCircle(mCenterX,mCenterY,radius,mHolePaint);
完整代码如下:
SplashView.java
public class SplashView extends View {
//旋转圆的画笔
private Paint mPaint;
//扩散圆的画笔
private Paint mHolePaint;
//属性动画
private ValueAnimator mValueAnimator;
//背景色
private int mBackgroundColor = Color.WHITE;
//颜色数组,代表6个球的颜色
private int[] mCircleColors;
//表示旋转圆的中心坐标
private float mCenterX;
private float mCenterY;
//表示斜对角线长度的一半,扩散圆最大半径
private float mDistance;
//6个小球的半径
private float mCircleRadius = 18;
//旋转大圆的半径
private float mRotateRadius = 90;
//当前大圆的旋转角度
private float mCurrentRotateAngle = 0F;
//当前大圆的半径
private float mCurrentRotateRadius = mRotateRadius;
//扩散圆的半径,即水波纹的半径
private float mCurrentHoleRadius = 0F;
//表示旋转动画的时长
private int mRotateDuration = 1200;
private SplashState mState;
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);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//开始绘制动画
if (mState==null){
mState=new RotateState();
}
mState.drawState(canvas);
}
@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;
//斜对角线的长度/2
mDistance= (float) (Math.hypot(w,h)/2);
}
/**
* 这个抽象类,对外提供drawState方法,供子类实现
*/
private abstract class SplashState{
abstract void drawState(Canvas canvas);
}
/**
* 1.旋转
* 需要在此绘制6个小球、背景
*/
private class RotateState extends SplashState{
private RotateState(){
//旋转一周,动画从0开始,直到Math.PI*2
mValueAnimator=ValueAnimator.ofFloat(0, (float) (Math.PI*2));
//执行模式,执行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();
//使onDraw()方法重新被调用
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);
//绘制6个小球
drawCircles(canvas);
}
}
/**
* 2.扩散聚合
*先向外扩散,再聚合
*/
private class MerginState extends SplashState{
private MerginState(){
//扩散的执行半径,从小圆的半径mCircleRadius开始,到大圆mRotateRadius结束
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();
//使onDraw()方法重新被调用
invalidate();
}
});
//监听动画的执行状态
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//切换到第二种动画
mState=new ExpandState();
}
});
//使动画反向执行
mValueAnimator.reverse();
}
@Override
void drawState(Canvas canvas) {
//绘制背景
drawBackground(canvas);
//绘制小球
drawCircles(canvas);
}
}
/**
* 3. 水波纹
* @param
*/
private class ExpandState extends SplashState{
private ExpandState(){
//扩散的执行半径,从小圆的半径mCircleRadius开始,到mDistance结束
mValueAnimator=ValueAnimator.ofFloat(mCircleRadius,mDistance);
//设置动画时长
mValueAnimator.setDuration(mRotateDuration);
//设置插值器。默认先加速后减速,这里使用线性插值器。
mValueAnimator.setInterpolator(new LinearInterpolator());
//监听动画执行过程
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//得到动画执行过程中的半径
mCurrentHoleRadius= (float) animation.getAnimatedValue();
//使onDraw()方法重新被调用
invalidate();
}
});
//使动画执行
mValueAnimator.start();
}
@Override
void drawState(Canvas canvas) {
drawBackground(canvas);
}
}
/**
* 绘制6个小球
* @param canvas
*/
private void drawCircles(Canvas canvas) {
//首先得到6个小球之间的角度
float rotateAngle = (float) (Math.PI*2/mCircleColors.length);
//开始小球的绘制
for (int i = 0; i < mCircleColors.length; i++) {
//每个小球的坐标:半径*cos值+圆心x坐标,半径*sin值+圆心y坐标
float angle = i*rotateAngle+mCurrentRotateAngle;//角度
float cx= (float) (Math.cos(angle)*mCurrentRotateRadius+mCenterX);//x坐标
float cy= (float) (Math.sin(angle)*mCurrentRotateRadius+mCenterY);//y坐标
//开始小球的绘制,传的参数是每个小球的颜色,即颜色数组
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);
}
}
private void init(Context context){
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHolePaint.setStyle(Paint.Style.STROKE);
mHolePaint.setColor(mBackgroundColor);
//颜色数组,取得是array目录下的颜色
mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
}
}
activity_main.xml
<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:src="@mipmap/content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.example.uimaster.canvas_splash.SplashView
android:id="@+id/splash"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>