王學崗高級UI(十)————屬性動畫

屬性動畫核心概念

  • 核心:
  • 1.ObjectAnimator 對象動畫(重點)
  • 2.ValueAnimator 值動畫(重點)
  • 3.PropertyValueHolder 用於多個動畫同時執行
  • 4.TypeEvaluator 估值器
  • 5.Interpolator 插值器
  • 6.AnimatorSet 動畫集合

package com.dn_alan.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.widget.FrameLayout;

import androidx.appcompat.app.AppCompatActivity;

/**
 * 屬性動畫,API3.0之後提出的動畫模式
 * 優點:
 * 1.不在侷限於View 對象,無對象也可以進行動畫處理
 * 2.不再侷限於4種基本變換:平移、旋轉、縮放 、透明度
 * 3.可以靈活的操作任意對象屬性,根據自己業務來實現自己想要的結果
 * 核心:
 * 1.ObjectAnimator  對象動畫(重點)
 * 2.ValueAnimator	  值動畫(重點)
 * 3.PropertyValueHolder 用於多個動畫同時執行
 * 4.TypeEvaluator 估值器
 * 5.Interpolator 插值器
 * 6.AnimatorSet 動畫集合
 *
 * 屬性動畫要求動畫作用的對象提供該屬性的set方法,屬性動畫根據傳遞的該屬性的初始值和最終值,
 * 以動畫的效果多次去調用set方法。每次傳給set方法的值都不一樣,確切來說是隨着時間的推移,所
 * 傳遞的值越來越接近最終值。
 */
public class MainActivity extends AppCompatActivity {
    private FrameLayout mMainView;
    private SplashView splashView;

    //    ------------------------加載動畫--------------------------------------
    Handler handler = new Handler();
    private void startLoadData() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //數據加載完畢,進入主界面--->開啓後面的兩個動畫
                splashView.splashDisappear();
            }
        },5000);//延遲時間
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        //將動畫層蓋在實際的操作圖層上
        mMainView = new FrameLayout(this);
        ContentView contentView = new ContentView(this);
        mMainView.addView(contentView);
        splashView = new SplashView(this);
        mMainView.addView(splashView);

        setContentView(mMainView);

        //後臺開始加載數據
        startLoadData();

    }
}

package com.dn_alan.myapplication;

import android.content.Context;
import android.widget.ImageView;

import androidx.appcompat.widget.AppCompatImageView;

/**
 * 瞭解 VSync 的同學都知道,Andorid 中的重繪就是由Choreographer在 1 秒內產生
 * 60 個 vsync 來通知 view tree 進行 view 的重繪的。而 vsync 產生後會調用它的監聽者
 * 回調接口 Choreographer.FrameCallback,也就是說,只要向Choreographer註冊了這個接口,
 * 就會每 1 秒裏收到 60 次回調。因此,在這裏就實現了不斷地調用 doAnimationFrame()
 * 來驅動動畫了。想必看到這裏,你應該明白了同學們常說的動畫掉幀的原因了吧。如果 view
 * 的繪製過於複雜,即在 15 ms 內無法完成,那麼就會使得中間某些幀跳過從而造成掉幀。
 */

public class ContentView extends AppCompatImageView {
    public ContentView(Context context) {
        super(context);
        setImageResource(R.mipmap.content);
    }
}

package com.dn_alan.myapplication;

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.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;

public class SplashView extends View {
    private ValueAnimator mAnimator;
    // 大圓(裏面包含很多小圓的)的半徑
    private float mRotationRadius = 90;
    // 每一個小圓的半徑
    private float mCircleRadius = 18;
    // 小圓圈的顏色列表,在initialize方法裏面初始化
    private int[] mCircleColors;
    // 大圓和小圓旋轉的時間
    private long mRotationDuration = 1200; //ms
    // 第二部分動畫的執行總時間(包括二個動畫時間,各佔1/2)
    private long mSplashDuration = 1200; //ms
    // 整體的背景顏色
    private int mSplashBgColor = Color.WHITE;

    /**
     * 參數,保存了一些繪製狀態,會被動態地改變* */
    //空心圓初始半徑
    private float mHoleRadius = 0F;
    //當前大圓旋轉角度(弧度)
    private float mCurrentRotationAngle = 0F;
    //當前大圓的半徑
    private float mCurrentRotationRadius = mRotationRadius;

    // 繪製圓的畫筆
    private Paint mPaint = new Paint();
    // 繪製背景的畫筆
    private Paint mPaintBackground = new Paint();

    // 屏幕正中心點座標
    private float mCenterX;
    private float mCenterY;
    //屏幕對角線一半
    private float mDiagonalDist;

    public SplashView(Context context) {
        super(context);
        init(context);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //當前View的中心店位置
        mCenterX = w/2f;
        mCenterY = h/2f;
        mDiagonalDist = (float) Math.sqrt((w*w+h*h))/2f;//勾股定律
    }

    private void init(Context context) {


        mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
        //畫筆初始化
        //消除鋸齒
        mPaint.setAntiAlias(true);
        mPaintBackground.setAntiAlias(true);
        //設置樣式---邊框樣式--描邊
        mPaintBackground.setStyle(Paint.Style.STROKE);
        mPaintBackground.setColor(mSplashBgColor);
    }

    public void splashDisappear(){
        //開啓後面兩個動畫
        //換模板--換狀態
        if(mState!=null && mState instanceof RotateState){
            //結束旋轉動畫
            RotateState rotateState = (RotateState) mState;
            rotateState.cancel();
            post(new Runnable() {
                @Override
                public void run() {
                    mState = new MergingState();
                }
            });
        }
    }

    private SplashState mState = null;
    //策略模式:State---三種動畫狀態
    private abstract class SplashState{
        //繪製不同的狀態
        public abstract  void drawState(Canvas canvas);
        public void cancel(){

            mAnimator.cancel();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mState==null){
            //開啓第一個旋轉動畫
            mState = new RotateState();
        }
        //調用繪製方法
        mState.drawState(canvas);
    }

    private void drawCircles(Canvas canvas) {
        //每個小圓之間的間隔角度 = 2π/小圓的個數
        float rotationAngle = (float) (2*Math.PI/mCircleColors.length);
        Log.i("barry","length------:"+mCircleColors.length);
        for (int i=0; i < mCircleColors.length; i++){
            /**
             * x = r*cos(a) +centerX
             * y=  r*sin(a) + centerY
             * 每個小圓i*間隔角度 + 旋轉的角度 = 當前小圓的真是角度
             */
            double angle = i*rotationAngle + mCurrentRotationAngle;
            float cx = (float) (mCurrentRotationRadius*Math.cos(angle) + mCenterX);
            float cy = (float) (mCurrentRotationRadius*Math.sin(angle) + mCenterY);
            mPaint.setColor(mCircleColors[i]);
            canvas.drawCircle(cx,cy,mCircleRadius,mPaint);
        }
    }

    private void  drawBackground(Canvas canvas) {
        if(mHoleRadius>0f){
            //得到畫筆的寬度 = 對角線/2 - 空心圓的半徑
            float strokeWidth = mDiagonalDist - mHoleRadius;
            mPaintBackground.setStrokeWidth(strokeWidth);
            //畫圓的半徑 = 空心圓的半徑 + 畫筆的寬度/2
            float radius = mHoleRadius + strokeWidth/2;
            canvas.drawCircle(mCenterX,mCenterY,radius,mPaintBackground);
        }else {
            canvas.drawColor(mSplashBgColor);
        }
    }


    /**
     * 1.旋轉動畫
     * 控制各個小圓的座標---控制小圓的角度變化----屬性動畫ValueAnimator
     */
    private class RotateState extends SplashState{
        public RotateState() {
            //1.動畫的初始工作;2.開啓動畫
            //花1200ms,計算某個時刻當前的角度是多少? 0~2π
            mAnimator = ValueAnimator.ofFloat(0f,(float)Math.PI*2);
            mAnimator.setInterpolator(new LinearInterpolator());
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    //計算某個時刻當前的大圓旋轉了的角度是多少?
                    mCurrentRotationAngle = (float) valueAnimator.getAnimatedValue();
                    postInvalidate();
                }
            });
            mAnimator.setDuration(mRotationDuration);
            mAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mAnimator.start();
        }


        @Override
        public void drawState(Canvas canvas) {
            //1.背景--擦黑板,塗成白色
            drawBackground(canvas);
            //2.繪製小圓
            drawCircles(canvas);
        }
    }


    /**
     * 2.聚合動畫
     * 要素:大圓的半徑不斷地變大--變小----》小圓的座標
     */
    private class MergingState extends SplashState{
        public MergingState() {
            //花1200ms,計算某個時刻當前的大圓半徑是多少? r~0中的某個值
            mAnimator = ValueAnimator.ofFloat(0, mRotationRadius);
            mAnimator.setDuration(mRotationDuration);
            mAnimator.setInterpolator(new OvershootInterpolator(10f));
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
//                    某個時刻當前的大圓半徑是多少?
                    mCurrentRotationRadius = (float)valueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            mAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mState = new ExpandState();
                }
            });
//            mAnimator.start();
            mAnimator.reverse();;
        }

        @Override
        public void drawState(Canvas canvas) {
            //1.背景--擦黑板,塗成白色
            drawBackground(canvas);
            //2.繪製小圓
            drawCircles(canvas);
        }
    }

    /**
     * 3.水波紋擴散動畫
     * 畫一個空心圓----畫一個圓,讓它的畫筆的粗細變成很大---不斷地減小畫筆的粗細。
     * 空心圓變化的範圍:0~ 對角線/2
     */
    private class ExpandState extends SplashState{
        public ExpandState() {
            //花1200ms,計算某個時刻當前的空心圓的半徑是多少? r~0中的某個值
            mAnimator = ValueAnimator.ofFloat(mCircleRadius, mDiagonalDist);
            Log.i("zhang_xin","mCircleRadius:"+mCircleRadius+",mDiagonalDist:"+mDiagonalDist);
            mAnimator.setDuration(mRotationDuration);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    //當前的空心圓的半徑是多少?
                    mHoleRadius = (float) valueAnimator.getAnimatedValue();
                    Log.i("zhang_xin","mHoleRadius:"+mHoleRadius);
                    invalidate();
                }
            });
            mAnimator.start();
        }

        @Override
        public void drawState(Canvas canvas) {
            drawBackground(canvas);
        }
    }

}

旋轉效果
在這裏插入圖片描述
放大縮小效果
在這裏插入圖片描述
圓形效果
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章