美文网首页Android开发经验谈Android技术知识Android开发
【Android进阶】我是如何实现——列车飞驰的加载动画的?

【Android进阶】我是如何实现——列车飞驰的加载动画的?

作者: 码农的书柜 | 来源:发表于2020-10-12 14:29 被阅读0次

    上周,Captain Train app被更名为了Trainline。这意味着我们必须改变颜色,图标,空白状态以及动画等以匹配我们的新商标。

    在创建新的加载动画的时候我们遇到了一些问题。因此我觉得这篇博客可能对开发者伙伴们有点用。实际上,我将尝试解释一下我们是如何设计与实现这个动画的。

    剧透:完全是由设备渲染,无视频,无gif,只有普通的View和矢量动画。

    背景

    在app更名之前,在加载搜索结果时我们显示的动画是这样的:

    这非常简单,就在app发布之前不到3小时内我们就完成了。实际上我们只使用了由三个drawable组成的animation-list drawable就完成了。

    对于Trainline,我们想要更好的效果。我们的设计师创建了一个漂亮的设计图。我们的挑战就是实现它。这里是我们最终得到的效果:

    Untitled.gif

    现在来看看我们是如何创建的。

    实现

    在安卓上有几种实现动画的技术。首先要指明的是我希望这个动画能在 Ice Cream Sandwich (4.0) 上运行,这是我们的minSdk。

    让我们来看看几种可能的方式:

    使用GIF。在web开发中这可能是一个很好的方法,但是安卓并不真正支持GIF。我也并不想为此而专门引入一个新的library。况且,我还必须处理不同的分辨率,这对于我们的APK来说是个负担。

    使用video。我不想使用MediaPlay之类的东西。理由还是分辨率和加重apk。

    使用AnimatedVectorDrawable。考虑到最近已经向后兼容,这个方法值得考虑。它很棒,但是会带来很多XML文件。而且这个过程也很难在设计师与开发者之间交流,因为设计师很难创建AnimatedVectorDrawable。而且我也不敢保证在低端设备上能流畅运行。

    使用视图动画。对于设计师和开发者来说这都比较简单,只有标准的VectorDrawable。视图动画被安卓framework很好的处理了,在过度绘制和性能上都有优化。

    设计

    越远越慢

    第一步就是设计视察动画,给人以速度和移动的感觉。我需要把原始图像分解成几个layer(层)并把它们显示在ImageView中:

    image

    然后,规则很简单:“越远越慢”。比如,风显然要比地平线移动得快。

    一个layer动画的规则也很简单,每个layer都是一样的:让layer从右到左移动,从窗口的一边开始,到另一边结束。重复模式为无限循环。layer越远,动画的duration(持续时间)越长。让我们来看一看代码示例:

    int translationXStart = totalViewWidth - mLayerView.getLeft() + OFFSET;
    int translationXEnd = -mLayerView.getLeft() - mLayerView.getWidth() - OFFSET;
    mLayerAnimator = ObjectAnimator.ofFloat(mLayerView,
                                            View.TRANSLATION_X,
                                            translationXStart,
                                            translationXEnd);
    mLayerAnimator.setRepeatCount(ObjectAnimator.INFINITE);
    mLayerAnimator.setRepeatMode(ObjectAnimator.RESTART);
    mLayerAnimator.setInterpolator(LINEAR_INTERPOLATOR);
    mLayerAnimator.setDuration(ANIMATION_DURATION);
    

    正如你看到的,这非常简单,这个例子只有X上的动画,但是某些layer(列车和鸟)可能需要Y上的动画。

    另一个重要的细节就是为了减少内存的占用,每个layer只包裹它的内容,这样Android只会绘制最小的部分。

    Untitled.gif

    你可以看到动画中铁轨是唯一没有移动的layer。

    目前我们并不支持RTL,如果哪天支持的话,改变动画的方向会很简单。

    Magic disappearance

    既然动画是无限循环的,我必须让layer到达两边的时候有个渐进的淡入淡出效果。一旦鸟到达了左边沿,它必须同时从右边沿进入。这里我使用了一个小技巧:用两个半透明的drawable作为背景。原理如下图:

    image

    火车自然的移动

    对于火车的实现我最开始只是上下移动1dp。当我跟我们的设计师说的时候,他建议我试下实现比这更自然的效果。毕竟火车不是这样震动的。

    我想创建一个遵循我自定义路径的interpolator:

    image

    最佳方式就是使用PathInterpolator,这个interpolator在开发者中间可能是最不知名的,但是我觉得它更强大,尤其是现在可以使用PathInterpolatorCompat做到向前兼容了以后。

    让我们来看看代码:

    private void prepareLocomotiveAnimation() {
            // First, create your path
            final Path path = new Path();
            path.lineTo(0.25f, 0.25f);
            path.lineTo(0.5f, -0.25f);
            path.lineTo(0.7f, 0.5f);
            path.lineTo(0.9f, -0.75f);
            path.lineTo(1f, 1f);
            // Create the ObjectAnimatpr
            mLocomotiveAnimator = ObjectAnimator.ofFloat(mLocomotiveView,
                                                         View.TRANSLATION_Y,
                                                         -1dp,
                                                         0);
            mLocomotiveAnimator.setRepeatCount(ObjectAnimator.INFINITE);
            mLocomotiveAnimator.setRepeatMode(ObjectAnimator.REVERSE);
            mLocomotiveAnimator.setDuration(ANIMATION_DURATION_LOCOMOTIVE);
            // Use the PathInterpolatorCompat
            mLocomotiveAnimator.setInterpolator(PathInterpolatorCompat.create(path));
    }
    

    其实没有啥神秘的。要注意的是,我应该使用quadTo替代ineTo去改进这个interpolation,那样会更流畅。还要注意的是动画的重复模式我使用了反向,这样可以变得连贯。

    让鸟飞起来

    这属于锦上添花的部分。让我们来看看我是如何做到的。

    我说过AnimatedVectorDrawable很棒是吧?刚好它是这个问题的最佳解决方案。第一步是导入你的VectorDrawable:

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:viewportWidth="18"
        android:viewportHeight="9"
        android:width="18dp"
        android:height="9dp">
        <path
            android:name="bird"
            android:pathData="M2,4c3,-1 6,-1.5 7,2c1,-3.5 4,-3 7,-2"
            android:strokeLineJoin="round"
            android:strokeWidth="2"
            android:strokeColor="#20416b"
            android:strokeMiterLimit="1.41421"
            android:strokeLineCap="round" />
    </vector>
    

    pathData的值是翅膀在上时的vector。

    然后,你需要一个animator让翅膀上下摆动。最关键的点是让两个path data的点具有相同数目。

    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:propertyName="pathData"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:valueFrom="M2,4c3,-1 6,-1.5 7,2c1,-3.5 4,-3 7,-2"
        android:valueTo="M4,7c3,-1 4,-4.5 5,-1c1,-3.5 2,0 5,1"
        android:valueType="pathType" />
    

    valueFrom的值是翅膀在上时的矢量,valueTo则是翅膀在下时的矢量。

    image

    最后我们使用AnimatedVectorDrawable把VectorDrawable和animator连系起来:

    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/animated_train_bird">
        <target
            android:animation="@animator/fly"
            android:name="bird" />
    </animated-vector>
    

    就如你看到的那样,这是一个演示了AnimatedVectorDrawables强大之处的基本例子。的确对动画产生了不小的润色效果。

    总结

    作为总结,我想说用户的反馈是很重要的!你总是需要别人来检测你的设计是否有意义、漂亮、自然。

    对于我而言,如果没有Enrico的视觉设计,我是无法完成的,也感谢Cyril 和 Thibaut 的反馈和检验。


    看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。

    另外整理收集的Android一线大厂面试完整考点、资料更新在我的【Github】,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!

    地址:【https://github.com/733gh/xiongfan】

    相关文章

      网友评论

        本文标题:【Android进阶】我是如何实现——列车飞驰的加载动画的?

        本文链接:https://www.haomeiwen.com/subject/vswupktx.html