1. 程式人生 > >自定義Loading大全之SwapLoading

自定義Loading大全之SwapLoading

  那一月 我搖動所有的經筒 不為超度 只為觸控你的指尖
  那一年 磕長頭匍匐在山路 不為覲見 只為貼著你的溫暖
  那一世 轉山轉水轉佛塔 不為修來生 只為途中與你相見
  那一瞬,我飛昇成仙,不為長生,只為佑你平安喜樂

概述

我們經常會看到一些很簡單又非常耐看的Loading載入動畫,它們又是怎麼用程式碼實現出來的呢?通過程式碼實現簡單的Loading動畫可以幫助我們學到很多東西,尤其是一些演算法的運用,任何事情都是從繁到簡,日積夜累,正所謂你看得多了你知道的也就多了。。。

還是老規矩,上效果圖:

swap

今天的主角登場:

swap

SwapLoading

開擼之前,我們來分析一下動畫:

  • 有5個小圓,編號 a,b,c,d,e a順時針向後面運動到b的位置,同時b順時針運動到a的位置,依次類推運動到e,e順時針運動到a的同時a運動到e形成一個閉環。

需要注意的是:a運動到b的軌跡是半圓。最開始我就踩了一個坑,把軌跡想成了二階貝塞爾曲線,一直糾結曲線公式,弄得我頭都大了。後來靈感一閃,我傻啊,為啥不用圓形軌跡,哈哈一下我就豁然開朗啦。

分析一下,是不是很清晰,然後開擼。

前面的一些建構函式就初始化就不講了,省略onMeasure,onSizeChanged方法。注意需要在onSizeChanged方法中處理padding屬性。

先來看一張簡單的草稿圖,圖畫得不好,見諒。

swap

我還是用 a,b,c,d,e來表示小球,a小球向上順時針運動,軌跡所經過的圓弧為180~360:

 mRotationAngle = DEGREE_180 + mValueAnimator * DEGREE_180;

mValueAnimator表示屬性動畫的值(0.0f~1.0f)

b小球所經過的圓弧為0~180度:

 mRotationAngle = DEGREE_180 - mValueAnimator * DEGREE_180;

a,b小球所運動的半徑為2小球之間的距離。那麼我們簡單的實現2個小球的運動軌跡。

swap

需要注意從b運動到c,b所經過的弧度為180~0:

 mRotationAngle 
= DEGREE_180 - mValueAnimator * DEGREE_180;

c經過的弧度為0~-180度:

  mNextRotationAngle = -mValueAnimator * DEGREE_180;

依次類推可以實現a,b,c,d小球的運動動畫,小球a到e就非常簡單了,注意運動半徑是2*小球間距。

我這裡貼出onDraw和startAnimator方法程式碼,以供參考:

onDraw方法:


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setColor(Color.WHITE);
        //繪製小球
        for (int i = 0; i < BALL_NUM; i++) {
            if (i == mCurrentBallIndex && i != BALL_NUM - 1) {
                if (isClockwiseRotation(i)) {
                    mRotationAngle = DEGREE_180 + mValueAnimator * DEGREE_180;
                    mNextRotationAngle = mValueAnimator * DEGREE_180;
                } else {
                    mRotationAngle = DEGREE_180 - mValueAnimator * DEGREE_180;
                    mNextRotationAngle = -mValueAnimator * DEGREE_180;
                }
            }

            if (i == mCurrentBallIndex) {
                mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

                mRotationX = (float) (ballSpacing * BALL_SPACING_RADIUS * Math.cos(Math.toRadians(mRotationAngle)));
                mRotationY = (float) (ballSpacing * BALL_SPACING_RADIUS * Math.sin(Math.toRadians(mRotationAngle)));

                if (i == BALL_NUM - 1) {
                    getFirstAndLastAngle();
                    //最後一個小球
                    canvas.drawCircle((float) (centerX + 2 * ballSpacing * Math.cos(Math.toRadians(mNextRotationAngle))),
                            (float) (centerY + 2 * ballSpacing * Math.sin(Math.toRadians(mNextRotationAngle))),
                            ballRadius, mPaint);
                } else {
                    canvas.drawCircle(centerX - mRadius + ballSpacing * (BALL_SPACING_RADIUS + i) + mRotationX,
                            centerY + mRotationY, ballRadius, mPaint);
                }
            }

            if ((i + 1) % BALL_NUM == getNextBallIndex()) {
                mPaint.setStyle(Paint.Style.STROKE);

                mRotationX = (float) (ballSpacing * BALL_SPACING_RADIUS * Math.cos(Math.toRadians(mNextRotationAngle)));
                mRotationY = (float) (ballSpacing * BALL_SPACING_RADIUS * Math.sin(Math.toRadians(mNextRotationAngle)));

                if (i == BALL_NUM - 1) {
                    getFirstAndLastAngle();
                    //第一個小球
                    canvas.drawCircle((float) (centerX + 2 * ballSpacing * Math.cos(Math.toRadians(mRotationAngle))),
                            (float) (centerY + 2 * ballSpacing * Math.sin(Math.toRadians(mRotationAngle))),
                            ballRadius, mPaint);

                } else {
                    canvas.drawCircle(centerX - mRadius + ballSpacing * (BALL_SPACING_RADIUS + i) + mRotationX,
                            centerY + mRotationY, ballRadius, mPaint);
                }
            }


            if (i != mCurrentBallIndex && i != getNextBallIndex()) {
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(centerX - mRadius + ballSpacing * i, centerY, ballRadius, mPaint);
            }

        }


        // centerX - mRadius + ballSpacing*0.5f      centerY


    }

startAnimator方法:


    //開始動畫
    public void startAnimator() {
        post(new Runnable() {
            @Override
            public void run() {
                ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
                animator.setDuration(5000);
                animator.setInterpolator(new LinearInterpolator());
                animator.setRepeatMode(ValueAnimator.RESTART);
                animator.setRepeatCount(ValueAnimator.INFINITE);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        mValueAnimator = (float) animation.getAnimatedValue();

                        postInvalidate();
                    }
                });
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        mCurrentBallIndex = 0;
                    }

                    @Override
                    public void onAnimationRepeat(Animator animation) {
                        super.onAnimationRepeat(animation);
                        mCurrentBallIndex = getNextBallIndex();

                    }
                });
                animator.start();
            }
        });
    }

這樣就實現了SwapLoading載入動畫。