本篇文章完全是根据HongChengDarren的文章仿写的,也是自己记录一下,以后看到类似的效果能知道怎么实现,轻喷!
一般做自定义view效果,首先是把效果放慢,然后分割,再一步一步的实现。首先动画效果分为三个部分:
第一:旋转动画,6个圆不停的旋转,如何让6个圆不停的旋转呢?
第二:聚合动画,6个圆向中间聚合。
第三:扩散动画,聚合完之后立马扩散。
首先解决第一个问题,想一想如何让6个圆不停的旋转,我们自定义view的时候如果想实现动画,要么通过改变一个变量然后结合invalidate方法来不停的绘制,要么通过动画来实现效果。如果用动画会有一些局限性,比如说需要用6张图片来执行这个动画,这样比较麻烦并且扩展性不好,所以只能从改变一个值然后invalidate来实现。
不管三七二十一,先绘制6个圆,这6个圆还有个起始位置,我日。起始位置怎么算?我们可以用360度除以6,每一份就是60度,然后用一个for循环在canvas.drawCircle()的时候算出小圆的x和y的位置,这样就好了。代码实现如下:
@Override void draw(Canvas canvas) {
/***
* 这里需要绘制6个圆,然后就得计算每个圆的圆心点,每个圆的x,y的位置该怎么计算呢?
* 首先把一个圆整分为6份(因为总共是6个颜色,你也可以根据你的需求划分), 然后通过画图得知,第一个圆的x=大圆中心点x的坐标+(cosA * 半径)
* y=大圆中心点y+(sinA * 半径)
*/
double percent = 2 * Math.PI / 6;
for (int i = 0; i < mCircleColors.length; i++) {
// 当前的角度=初始角度+旋转角度(percent * i+mCurrentRotationValue)
float cx = (float) (circleCX + (Math.cos(percent * i + mCurrentRotationValue) * mBigCircleRadius));
float cy = (float) (circleCY + Math.sin(percent * i + mCurrentRotationValue) * mBigCircleRadius);
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx, cy, mSmallCircleRadius, mPaint);
}
}
有几个变量解释一下,mBigCircleRadius是大圆的半径,那个是大圆呢?小圆到中心点的距离,这个我们可以写死,写成屏幕宽度/4就行,mSmallCircleRadius这个是小圆半径,也是写死的。mCurrentRotationValue这个是旋转的角度,这个角度动态变化的情况下,就可以实现我们想要的效果。如何让这个角度动态变化呢?可以使用属性动画来不断的改变这个值,属性动画从0到2π之间变化。
第二个问题:聚合动画
其实看效果图会发现,6个圆是先向后再向前,刚开始我以为是通过设置三个属性动画值来达到这种效果,但是看作者的代码,才明白直接用AnticipateInterpolator这个插值器就好,666啊。下边我们分析如何让6个圆向中间聚合?其实我们是6个小圆围绕一个大圆在旋转,小圆到大圆的距离就是半径,我们可以改变这个半径的值,就可以实现我们的效果,代码如下:
private class MergeAnimator extends LoadingState {
private ValueAnimator valueAnimator;
public MergeAnimator() {
init();
}
private void init() {
valueAnimator = ValueAnimator.ofFloat(mBigCircleRadius, 0);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mCurrentMergeRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
mLoadingState = new ExpendAnimator();
}
});
// 开始的时候向后,然后向前甩
valueAnimator.setInterpolator(new AnticipateInterpolator(3f));
valueAnimator.setDuration(MERGE_DURATION);
valueAnimator.start();
}
@Override void draw(Canvas canvas) {
/***
* 这里需要绘制6个圆,然后就得计算每个圆的圆心点,每个圆的x,y的位置该怎么计算呢?
* 首先把一个圆整分为6份(因为总共是6个颜色,你也可以根据你的需求划分), 然后通过画图得知,第一个圆的x=大圆中心点x的坐标+(cosA * 半径)
* y=大圆中心点y+(sinA * 半径)
*/
double percent = 2 * Math.PI / mCircleColors.length;
for (int i = 0; i < mCircleColors.length; i++) {
// 当前的角度=初始角度+旋转角度(percent * i+mCurrentRotationValue)
float cx = (float) (circleCX + (Math.cos(percent * i + mCurrentRotationValue) * mCurrentMergeRadius));
float cy = (float) (circleCY + Math.sin(percent * i + mCurrentRotationValue) * mCurrentMergeRadius);
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx, cy, mSmallCircleRadius, mPaint);
}
}
}
最后一个是扩散动画,我这里写的比较简单,仅仅只是绘制一个圆,这个圆的半径从0变化到整个屏幕的对角线的一半,为何是这个值?因为如果只是宽或高的一半,那么这个圆是填充不了整个屏幕。代码如下:
private class ExpendAnimator extends LoadingState {
private ValueAnimator valueAnimator;
public ExpendAnimator() {
init();
}
private void init() {
valueAnimator = ValueAnimator.ofFloat(0, mDiagonalRadius);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mSpreadRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(RATION_DURATION);
valueAnimator.start();
}
@Override void draw(Canvas canvas) {
mPaint.setColor(Color.WHITE);
canvas.drawCircle(circleCX, circleCY, mSpreadRadius, mPaint);
}
}
全部代码如下:
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.view.View;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.LinearInterpolator;
import com.wxj.design.pattern.R;
import java.util.Map;
/**
*
* 1.旋转动画 6个圆旋转
*
* 2.聚合动画 6个圆向中间聚合
*
* 3.扩散动画 扩散到整个界面 先绘制一个圆,圆的半径是屏幕对角线的一半
*
* Created by wuxiaojun on 2018/11/20.
*/
public class YahuView extends View {
private final int RATION_DURATION = 2000;
private final int MERGE_DURATION = 1000;
private int[] mCircleColors;
private int mSmallCircleRadius; // 小圆半径
private int mBigCircleRadius; // 大圆半径
private int circleCX, circleCY; // 大圆的中心点
private Paint mPaint; // 画笔
private LoadingState mLoadingState;
private float mCurrentRotationValue = 0f; // 旋转动画的值
private float mCurrentMergeRadius = mBigCircleRadius; // 当前聚合动画的半径
private float mDiagonalRadius; // 屏幕的对角线的一半
private float mSpreadRadius; // 扩散圆的半径
public YahuView(Context context) {
this(context, null);
}
public YahuView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public YahuView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCircleColors = getContext().getResources().getIntArray(R.array.splash_circle_colors);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mSmallCircleRadius = getMeasuredWidth() / 26;
mBigCircleRadius = getMeasuredWidth() / 3;
circleCX = getMeasuredWidth() / 2;
circleCY = getMeasuredHeight() / 2;
mDiagonalRadius = (float) Math.sqrt((circleCX * circleCY + circleCY * circleCY));
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mLoadingState == null) {
mLoadingState = new RotationAnimator();
}
mLoadingState.draw(canvas);
}
public void disapear() {
// 关闭旋转动画
if (mLoadingState instanceof RotationAnimator) {
((RotationAnimator) mLoadingState).cancel();
}
// 开启聚合动画
mLoadingState = new MergeAnimator();
}
private abstract class LoadingState {
abstract void draw(Canvas canvas);
}
private class ExpendAnimator extends LoadingState {
private ValueAnimator valueAnimator;
public ExpendAnimator() {
init();
}
private void init() {
valueAnimator = ValueAnimator.ofFloat(0, mDiagonalRadius);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mSpreadRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(RATION_DURATION);
valueAnimator.start();
}
@Override void draw(Canvas canvas) {
mPaint.setColor(Color.WHITE);
canvas.drawCircle(circleCX, circleCY, mSpreadRadius, mPaint);
}
}
private class MergeAnimator extends LoadingState {
private ValueAnimator valueAnimator;
public MergeAnimator() {
init();
}
private void init() {
valueAnimator = ValueAnimator.ofFloat(mBigCircleRadius, 0);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mCurrentMergeRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
mLoadingState = new ExpendAnimator();
}
});
// 开始的时候向后,然后向前甩
valueAnimator.setInterpolator(new AnticipateInterpolator(3f));
valueAnimator.setDuration(MERGE_DURATION);
valueAnimator.start();
}
@Override void draw(Canvas canvas) {
/***
* 这里需要绘制6个圆,然后就得计算每个圆的圆心点,每个圆的x,y的位置该怎么计算呢?
* 首先把一个圆整分为6份(因为总共是6个颜色,你也可以根据你的需求划分), 然后通过画图得知,第一个圆的x=大圆中心点x的坐标+(cosA * 半径)
* y=大圆中心点y+(sinA * 半径)
*/
double percent = 2 * Math.PI / mCircleColors.length;
for (int i = 0; i < mCircleColors.length; i++) {
// 当前的角度=初始角度+旋转角度(percent * i+mCurrentRotationValue)
float cx = (float) (circleCX + (Math.cos(percent * i + mCurrentRotationValue) * mCurrentMergeRadius));
float cy = (float) (circleCY + Math.sin(percent * i + mCurrentRotationValue) * mCurrentMergeRadius);
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx, cy, mSmallCircleRadius, mPaint);
}
}
}
private class RotationAnimator extends LoadingState {
private ValueAnimator mValueAnimator;
public RotationAnimator() {
init();
}
private void init() {
mValueAnimator = ValueAnimator.ofFloat(0f, (float) (2f * Math.PI));
mValueAnimator.setDuration(RATION_DURATION);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mCurrentRotationValue = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.start();
}
@Override void draw(Canvas canvas) {
/***
* 这里需要绘制6个圆,然后就得计算每个圆的圆心点,每个圆的x,y的位置该怎么计算呢?
* 首先把一个圆整分为6份(因为总共是6个颜色,你也可以根据你的需求划分), 然后通过画图得知,第一个圆的x=大圆中心点x的坐标+(cosA * 半径)
* y=大圆中心点y+(sinA * 半径)
*/
double percent = 2 * Math.PI / mCircleColors.length;
for (int i = 0; i < mCircleColors.length; i++) {
// 当前的角度=初始角度+旋转角度(percent * i+mCurrentRotationValue)
float cx = (float) (circleCX + (Math.cos(percent * i + mCurrentRotationValue) * mBigCircleRadius));
float cy = (float) (circleCY + Math.sin(percent * i + mCurrentRotationValue) * mBigCircleRadius);
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx, cy, mSmallCircleRadius, mPaint);
}
}
public void cancel() {
mValueAnimator.cancel();
}
}
}
activity的代码
public class YahuActivity extends Activity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_yahu);
final YahuView yahuView = (YahuView) findViewById(R.id.id_yahuview);
new Handler() {
@Override public void handleMessage(Message msg) {
yahuView.disapear();
}
}.sendEmptyMessageDelayed(1, 5000);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.wxj.design.pattern.view.YahuView
android:id="@+id/id_yahuview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
颜色值
<color name="orange">#FF9600</color>
<color name="aqua">#02D1Ac</color>
<color name="yellow">#FFd200</color>
<color name="blue">#00c6ff</color>
<color name="green">#00e099</color>
<color name="pink">#ff3891</color>
<array name="splash_circle_colors">
<item>@color/blue</item>
<item>@color/pink</item>
<item>@color/aqua</item>
<item>@color/yellow</item>
<item>@color/orange</item>
<item>@color/green</item>
</array>
再次声明,本篇文章仅仅只是自己记录。具体可看原创作者大佬的博客