效果:
整個效果分爲旋轉、擴散聚合、水波紋效果,首先在定義好一些變量後,要先定義一個抽象類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>