Android 自定义 View - 小米 MIUI8 天气动画(晴天)

介绍

升级MIUI8之后,小米自带的天气应用晴天的时候动画效果很赞。然后就捣鼓了几下写了个简易的demo。
我们先来看看实现后的效果:
正常模式:

img1

View跟随手机摇晃后移动:
img2

下载演示demo
项目地址Github

分析

动画分解

  1. 飞翔的鸟群
  2. 树的枝干会倾斜15~30度,并在达到倾斜的最大角度后抛出树叶,等树叶消失后,回到最初位置。
  3. 树叶每次抛出角度不固定,并且在下落一定距离后渐渐消失。
  4. 左右摇晃手机,背景的山会随着手机移动。

实现

(1) 下载app,然后将其后缀名改为zip解压获取我们需要的资源图片:

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) 树动画


思路:看到这素材就知道树是拼凑出来的。树动画的实现,通过观察可以发现是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值