自定義View實現Canvas炫酷效果

效果:
在這裏插入圖片描述
整個效果分爲旋轉、擴散聚合、水波紋效果,首先在定義好一些變量後,要先定義一個抽象類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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章