1. 程式人生 > >自定view--小清新載入進度動畫特效

自定view--小清新載入進度動畫特效

這裡寫圖片描述

實現上面的效果涉及到左右兩個小圓點、中間小圓點、線條的繪製,同時涉及到屬性動畫及二階貝塞爾曲線的使用。
這裡寫圖片描述

A點和B點的兩個小圓點比較好繪製,確定相應的座標後,呼叫canvas.drawCircle()方法進行繪製;

A點的X就是:getWidth(自定義控制元件的寬度)/2-mLineWidth(A點到B點的長度,即線條的長度)/2;
C點的X就是:getWidth(自定義控制元件的寬度)/2+mLineWidth(A點到B點的長度,即線條的長度)/2;
A點和C點的Y是一樣的都是:getHeight()/2

接下來就是繪製線條和中間的小球,將小球的狀態定義為3種:下墜,上升及自由落體,在小球下墜、上升或者自由落體時,呼叫mPath.moveTo()和mPath.quadTo()進行左右貝塞爾曲線的繪製,A點和C點的座標在繪製左右兩個小點的時候就已經得到了,

B點的X座標是:getWidth(自定義控制元件的寬度)/2+mLineWidth(A點到B點的長度,即線條的長度)/2;
Y的座標在上升或者下墜過程中就有區別:下墜Y座標:getHeight() / 2 + mDownDistance(下墜的距離);上升或者自由落地Y座標:getHeight() / 2 + (distance(定義的一個距離常量) - mUpDistance(上升的距離))

D和E點的座標可以通過除錯後獲取到最佳的座標點,相應的三個點的座標值獲取後,就可以繪製相應的貝塞爾曲線了;

剩下就只有中間小球的座標位置了,不管下墜、上升或者自由落體,X對應的座標都是getWidth(自定義控制元件的寬度)/2;

但是對應的Y的座標就會有區別:
下墜:getHeight() / 2 + mDownDistance(下墜的距離) - mPointSize(小球的半徑)
正常上升:getHeight() / 2 + (distance - mUpDistance) - mPointSize
自由落地:getHeight() / 2 - freeBallDistance(自由落體的距離) - mPointSize

獲取到對應的座標點後,呼叫onDraw()方法進行繪製,小球的上下運動是通過ValueAnimator來控制的;

實現主要程式碼(程式碼中有相應的註釋):

public class
LoadView extends SurfaceView implements SurfaceHolder.Callback {
private static final int STATE_DOWN = 1; private static final int STATE_UP = 2; private Paint mPaint; private Path mPath; //線的顏色 private int mLineColor = Color.WHITE; //點的顏色 private int mPointColor = Color.BLACK; //點的大小 private int mPointSize = 10; //線的寬度 private int mLineWidth = 200; //線的高度 private int mLineHeight = 2; //往下走的距離 private float mDownDistance; private static final int distance=50; //往上走的距離 private float mUpDistance; private float freeBallDistance; //向下運動 private ValueAnimator downController; //向上運動 private ValueAnimator upController; //自由落體 private ValueAnimator freeDownController; //動畫集合 private AnimatorSet animatorSet; private int state; //是否向上彈起 private boolean isBounced = false; private boolean isBallFreeUp = false; private boolean isUpControllerDied = false; private boolean isAnimationShowing = false; public LoadView(Context context) { this(context, null); } public LoadView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LoadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } /** * 初始化 * * @param context * @param attrs */ private void init(Context context, AttributeSet attrs) { //初始化自定義屬性 initAttributes(context, attrs); //初始化畫筆 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStrokeWidth(mLineHeight); mPaint.setStrokeCap(Paint.Cap.ROUND);//圓形線帽 //初始化path mPath = new Path(); getHolder().addCallback(this); initController(); } private void initController() { downController = ValueAnimator.ofFloat(0, 1); //設定動畫持續的時長 downController.setDuration(500); //設定差值器 downController.setInterpolator(new DecelerateInterpolator()); //監聽動畫執行 downController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mDownDistance = distance * (float) animation.getAnimatedValue(); //進行重繪 postInvalidate(); } }); downController.addListener(new SimpleAnimatorListener() { @Override public void onAnimationStart(Animator animation) { state = STATE_DOWN; } }); upController = ValueAnimator.ofFloat(0, 1); upController.setDuration(900); upController.setInterpolator(new DampingInterpolator()); upController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mUpDistance = distance * (float) animation.getAnimatedValue(); if (mUpDistance >= distance) { //進入自由落體狀態 isBounced = true; if (!freeDownController.isRunning() && !freeDownController.isStarted() && !isBallFreeUp) { freeDownController.start(); } } postInvalidate(); } }); upController.addListener(new SimpleAnimatorListener() { @Override public void onAnimationStart(Animator animation) { state = STATE_UP; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); isUpControllerDied=true; } }); freeDownController = ValueAnimator.ofFloat(0, 6.8f); freeDownController.setDuration(600); freeDownController.setInterpolator(new DecelerateInterpolator()); freeDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //一個公式解決上升減速和下降加速 float t = (float) animation.getAnimatedValue(); freeBallDistance = 34 * t - 5 * t * t; if (isUpControllerDied) { postInvalidate(); } } }); freeDownController.addListener(new SimpleAnimatorListener() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); isBallFreeUp = true; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); isAnimationShowing = false; //迴圈第二次 startTotalAnimations(); } }); //通過AnimatorSet集合開啟所有動畫 animatorSet = new AnimatorSet(); //設定動畫開啟的先後順序 animatorSet.play(downController).before(upController); //監聽動畫的執行 animatorSet.addListener(new SimpleAnimatorListener() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); isAnimationShowing = true; } }); } /** * 開啟所有動畫 */ public void startTotalAnimations() { if (isAnimationShowing) { return; } if (animatorSet.isRunning()) { animatorSet.end(); animatorSet.cancel(); } isBounced = false; isBallFreeUp = false; isUpControllerDied = false; animatorSet.start(); } /** * 初始化自定義屬性 * * @param context * @param attrs */ private void initAttributes(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadView); mLineColor = array.getColor(R.styleable.LoadView_line_color, mLineColor); mLineWidth = array.getDimensionPixelOffset(R.styleable.LoadView_line_width, mLineWidth); mLineHeight = array.getDimensionPixelOffset(R.styleable.LoadView_line_height, mLineHeight); mPointSize = array.getDimensionPixelOffset(R.styleable.LoadView_point_size, mPointSize); mPointColor = array.getColor(R.styleable.LoadView_point_color, mPointColor); array.recycle(); } @Override protected void onDraw(Canvas canvas) { //1.繪製一條繩子 /** * 一條繩子用左右兩部分的二階貝塞爾來繪製 */ mPaint.setColor(mLineColor); mPath.reset(); //起始點 mPath.moveTo(getWidth() / 2 - mLineWidth / 2, getHeight() / 2); if (state == STATE_DOWN) { //下墜 //左邊的貝塞爾 mPath.quadTo( (float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375), getHeight() / 2 + mDownDistance, getWidth() / 2, getHeight() / 2 + mDownDistance); //右邊的貝塞爾 mPath.quadTo( (float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375), getHeight() / 2 + mDownDistance, getWidth() / 2 + mLineWidth / 2, getHeight() / 2); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath, mPaint); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mPointColor); canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - mPointSize, mPointSize, mPaint); } else if (state == STATE_UP) { //向上彈 //繩子照樣繪製 //左邊的貝塞爾 mPath.quadTo( (float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375), getHeight() / 2 + (distance - mUpDistance), getWidth() / 2, getHeight() / 2 + (distance - mUpDistance)); //右邊的貝塞爾 mPath.quadTo( (float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375), getHeight() / 2 + (distance - mUpDistance), getWidth() / 2 + mLineWidth / 2, getHeight() / 2); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath, mPaint); //第三種狀態 自由落體 //2.繪製彈性小球 mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mPointColor); if (!isBounced) { //正常上升 canvas.drawCircle(getWidth() / 2, getHeight() / 2 + (distance - mUpDistance) - mPointSize, mPointSize, mPaint); } else { //自由落體 canvas.drawCircle(getWidth() / 2, getHeight() / 2 - freeBallDistance - mPointSize, mPointSize, mPaint); } } //3.繪製兩邊的小球 兩邊的固定點的圓 mPaint.setColor(mPointColor); mPaint.setStyle(Paint.Style.FILL); //繪製左邊小圓 canvas.drawCircle(getWidth() / 2 - mLineWidth / 2, getHeight() / 2, mPointSize, mPaint); //繪製右邊小圓 canvas.drawCircle(getWidth() / 2 + mLineWidth / 2, getHeight() / 2, mPointSize, mPaint); super.onDraw(canvas); } @Override public void surfaceCreated(SurfaceHolder holder) { //鎖定畫布 Canvas canvas = holder.lockCanvas(); //進行繪製 draw(canvas); //解除鎖定 holder.unlockCanvasAndPost(canvas); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
/**
 * Created by Administrator on 2017/12/23.
 * 自定義加速器
 */

public class DampingInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float input) {
        return (float) (1 - Math.exp(-3 * input) * Math.cos(10 * input));
    }
}
/**
 * Created by Administrator on 2017/12/23.
 實現AnimatorListener介面,在監聽動畫的時候可以建立SimpleAnimatorListener抽象類子類
 根據需要去重寫相應的方法
 */

public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
    @Override
    public void onAnimationStart(Animator animation) {

    }

    @Override
    public void onAnimationEnd(Animator animation) {

    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
}

在LoadView類中提供了startTotalAnimations()方法,在對應的activity頁面中開啟動畫就ok了。

public class MainActivity extends AppCompatActivity {
    private LoadView qp;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        qp = (LoadView) findViewById(R.id.qp);
        //開啟動畫
        qp.startTotalAnimations();
    }
}

原始碼地址:
https://pan.baidu.com/s/1o865soi