Android 自定義 View - 小米 MIUI8 天氣動畫(晴天)

咆哮的大蘑菇發表於2017-02-10

介紹

升級MIUI8之後,小米自帶的天氣應用晴天的時候動畫效果很贊。然後就搗鼓了幾下寫了個簡易的demo。
我們先來看看實現後的效果:
正常模式:

Android 自定義 View - 小米 MIUI8 天氣動畫(晴天)
img1

View跟隨手機搖晃後移動:
Android 自定義 View - 小米 MIUI8 天氣動畫(晴天)
img2

下載演示demo
專案地址Github

分析

動畫分解

  1. 飛翔的鳥群
  2. 樹的枝幹會傾斜15~30度,並在達到傾斜的最大角度後丟擲樹葉,等樹葉消失後,回到最初位置。
  3. 樹葉每次丟擲角度不固定,並且在下落一定距離後漸漸消失。
  4. 左右搖晃手機,背景的山會隨著手機移動。

實現

(1) 下載app,然後將其字尾名改為zip解壓獲取我們需要的資源圖片:

Android 自定義 View - 小米 MIUI8 天氣動畫(晴天)
img3

(2) 實現鳥群動畫
思路:通過解壓後得到的素材可以發現鳥扇動翅膀的實現類似於幀動畫,鳥群的運動軌跡是曲線,我們可以使用貝塞爾曲線,為了能夠實時移動鳥群,我們需要獲取貝塞爾曲線上面的點座標,這裡我是使用函式來計算出點座標,也可以用PathMeasure來實現。綜上,用ValueAnimator實現實時計算鳥群移動點座標,設定一個計數器,用來更新鳥的Bitmap實現幀動畫效果。
核心程式碼

        isDrawBirds = true;
        BezierEvaluator bezierEvaluator = new BezierEvaluator(newPointF(birdControlPointX, birdControlPointY));
        final ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator,
                new PointF(birdStartPointX, birdStartPointY),
                new PointF(birdEndPointX, birdEndPointY));
        anim.setDuration(8000);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                PointF point = (PointF) valueAnimator.getAnimatedValue();
                birdMovePointX = (int) point.x;
                birdMovePointY = (int) point.y;
                ++birdTimes;
                if (birdTimes % 8 == 0){
                   updatebirds(birdTimes);
                }
                invalidate();
            }
        });
        anim.setInterpolator(new LinearInterpolator());
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                super.onAnimationRepeat(animation);
                birdTimes = 0 ;
            }
        });
        anim.setRepeatMode(ValueAnimator.RESTART);
        anim.setRepeatCount(ValueAnimator.INFINITE);
        anim.start();
    private void updatebirds(int birdTimes){
        bird = birdsList.get(birdTimes % 16);
    }複製程式碼

(2) 樹動畫

Android 自定義 View - 小米 MIUI8 天氣動畫(晴天)

思路:看到這素材就知道樹是拼湊出來的。樹動畫的實現,通過觀察可以發現是treeBallLeft,treeBallMiddle,treeBallRight,通過繞treeBranch的中心點實現。
核心程式碼
  valueAnimatorTree = new ValueAnimator().ofFloat(0, 15);
        valueAnimatorTree.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                rotateValueTree = 360 - (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimatorTree.addPauseListener(new Animator.AnimatorPauseListener() {
            @Override
            public void onAnimationPause(Animator animation) {
                rotateValueTree = 0;
                isDrawTree = true;
                startAnimLeaf();
            }

            @Override
            public void onAnimationResume(Animator animation) {

            }
        });
        valueAnimatorTree.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                ++timesTree;
                if (timesTree % 2 == 1)
                    valueAnimatorTree.pause();
            }
        });
        valueAnimatorTree.setDuration(2000);
        valueAnimatorTree.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimatorTree.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimatorTree.setInterpolator(new LinearInterpolator());
        valueAnimatorTree.start();複製程式碼

(3) 樹葉動畫
思路:樹葉的初始位置,將樹葉藏在treeBallMiddle裡面。然後執行路徑動畫,執行路徑動畫的同時,根據getAnimatedFraction() 獲取動畫進行的百分比計算出樹葉的旋轉角度,動畫到執行75%的時候執行透明動畫就行了
核心程式碼

  public void startAnimLeaf() {
        randomRotate = Math.random() * 90;
        BezierEvaluator bezierEvaluator = new BezierEvaluator(new PointF(treeControlPointX, treeControlPointY));
        final ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator,
                new PointF(treeStartPointX, treeStartPointY),
                new PointF(treeEndPointX, treeEndPointY));
        anim.setDuration(4000);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                PointF point = (PointF) valueAnimator.getAnimatedValue();
                moveLeafX = point.x;
                moveLeafY = point.y;
                float fraction =valueAnimator.getAnimatedFraction();
                rotateValueLeaf = 90 * fraction;
                if (fraction>=0.75 && valueAnimatorAlpha.isRunning()== false){
                    startAlpha();
                }
                invalidate();
            }
        });
        anim.setInterpolator(new LinearInterpolator());
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                isDrawTree = false;
                valueAnimatorTree.resume();
                invalidate();
            }
        });
        anim.start();
    }
    private void startAlpha(){
        valueAnimatorAlpha = ValueAnimator.ofFloat(0,1);
        valueAnimatorAlpha.setDuration(1000);
        valueAnimatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float fraction = (float) valueAnimator.getAnimatedValue();
                treeLeafPaint.setAlpha((int) (255 - 255 * fraction));
            }
        });
        valueAnimatorAlpha.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                treeLeafPaint = new Paint();
            }
        });
        valueAnimatorAlpha.setInterpolator(new LinearInterpolator());
        valueAnimatorAlpha.start();
    }複製程式碼

(4) 背景
思路:通過BitmapFactory.Option來壓縮背景圖片的大小,保證View的流暢性。
搖晃手機的同時,背景跟著動的實現:設定背景圖片的時候,寬是螢幕寬度+maxoffsetX*2。通過implements SensorEventListener,獲取搖擺手機時候晃動的百分比,然後在onDraw中呼叫

        currentOffsetX = maxOffsetX * mProgress;
        canvas.translate(currentOffsetX, 0);複製程式碼

搖晃手機獲取差值參考Github:PanoramaImageView

下載演示demo
專案地址Github

相關文章