Android Animation Made Easy

原文链接 Android Animation Made Easy

动画在任何一个GUI系统中都是一个非常重要的设计元素,它可以让交互变得优雅,让界面变得炫酷,让操作变得更加的舒畅,让状态过渡变得更加的顺滑,对视觉效果有极大的提升,时而提升用户体验,特别是对于移动应用来说,更是如此。就好比水果平台,最为吸引人的地方就在于其炫酷流畅的动画效果。早期的Android,在动画这一块确实差,不过,近些年,随着谷歌不断的加大力度在提升,现在来说安卓在动画这一块已经跟水果差不多了。今天就来聊一聊关于动画的话题。

动画的种类

一般来说动画分为二个种类:

逐帧动画(Frame Animation)也叫做Drawable Animation

也就是电影胶片式的,一张张不同的画连在一起播放,比较简单,只需要准备足够帧数(数量)的图片,就可以了。缺点也比较明显,需要比较多的资源(图片,存储空间,内存空间以及CPU资源)。并且灵活性非常的差,不能让普通的一段文字或者一个按扭进行动画。 针对某些特别简单的动画可以用此方式来实现,比如像简单的进度条,或者滑动引导提示等,具体的方式就是:

ini 复制代码
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">    
    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

补间动画(Tween Animation)

补间动画比帧动画就要高级一些,因为充分利用了计算机的特性,只需要告诉起始状态和结束状态,然后让计算机去计算中间的状态,再不用把每一帧都告诉计算机了。在安卓中就是View Animation,以及后来的强大的Property Animation

动画的基本原理

动画,其实就是一组快速播放的幻灯片,每一张(每一帧)的状态略有不同,快速连起来播放,由于人的眼睛有视觉残留效应,这就形成了动画。对于计算机程序来说,一般的动画就是给定对象的初始和终末状态,在一定时间内,不断的计算中间过程,并以视觉的方式展示出来,这就是动画。

动画的关键要素

一个动画必须要有以下关键的要素:

  • 时长(Duration),也就 说播放动画的总时长,系统默认是300ms
  • 时间插值器(Interpolation),就是动画的关键参数随时间要如何变化
  • 重播(Repeat),包括重播的次数以及方式,次数好理解,方式的意思是,可以顺序的一遍一遍的播,也是可以反着播
  • 延迟(Delay),动画启动的延时,通常用在动画组合里面。

View Animation

这是从安卓一开始就支持的动画方式,仅能对View对象生效,使用起来也比较方便和简单,通过组合和自定义插值器,足以实现常用的视觉变幻(如渐变,旋转,缩放和位移)。

主要有四种位移(translation)透明度(alpha)旋转(rotation)缩放(scaling)。还可以以集合的方式来把几个动画合在一起播放。可以设置动画的时长(duration),速率(interpolator),重复和事件的监听。 一些使用建议:

  • 所有这些东西都是放在android.view.animation包下面的,所以这些东西只能用于View对象。其实绝大多数时候,这也不是问题,除了View,还有啥要做动画呢。

  • TranslateAnimation只能是直线运动,如果要曲线,就得自定义,可以参考这个

  • scale可以实现水平或者垂直翻转。

  • 动画的触发是当View需要invalidate的时候就会触发setAnimation指定的动画。所以,如下代码会正常触发:

    ini 复制代码
    TextView title; // 原来是GONE的
    title.setVisibility(View.VISIBLE);
    title.setAnimation(new AlphaAnimation(0.3f, 1f);

    不一定非要startAnimation

  • 要注意动画前后View的状态。这个比较难受。因为动画过程仅是放一遍电影(动画过程中仅是在View的绘制的时候对Canvas做变幻),对View本身并没有影响,通常的做法是给View Animation加上Listener,在onAnimationEnd的时候去设置目标状态。

总的来说,View animation简单易用,大部分场景是可以满足需求的,早期版本确实有一些缺陷,内部状态在动画过程中会有问题,但是最近新的Android版本上面,已没有大问题,所以当能满足需求时,使用也没有问题。

属性动画(Property Animation)

就像名字暗示那样,从3.0开始一套新的动画API出现了,可以描述为在一段时间内以一定的方式来改变某一个属性,是这样的方式来做动画。所以,它也可以做动画以外的事情。这套API的核心思想是在一段时间内,让某些属性随着时间改变(有点像中学的物理题)。

属性动画就是根据时间来改变某一对象(不一定非要是View)的某一个属性,至于某一时刻属性变化的值所产生的后果,由使用者自定义,因此你可以把它应用于任何对象。

它也与View一样,可以组合,可以设置事件监听。

与View动画最大的区别在于,View动画仅是按要求放一遍电影,不会对View的实际属性产生影响,因此,动画过程中以及完成后View仍是在原来的位置,属性也不会变化。而属性动画则不是,它会直接改变View的属性,所以有些时候这个优势会很方便,比如实现收起与展开的动画时Property动画会明显的优势:

比如,对于一个可以收起和弹出的动画,就可以这样来实现:

收起动画:

java 复制代码
private void animateCollapse() {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator translate = ObjectAnimator.ofFloat(mStatusPanel, "translationY", 0f, mTranslationY);
        ObjectAnimator alpha = ObjectAnimator.ofFloat(mStatusPanel, "alpha", 1f, 0.75f);
        set.setDuration(250);
        set.setInterpolator(new AccelerateDecelerateInterpolator());
        set.playTogether(translate, alpha);
        set.start();
    }

弹出动画:

java 复制代码
private void animateExpansion() {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator translate = ObjectAnimator.ofFloat(mStatusPanel, "translationY", mTranslationY, 0);
        ObjectAnimator alpha = ObjectAnimator.ofFloat(mStatusPanel, "alpha", 0.75f, 1f);
        set.setDuration(250);
        set.setInterpolator(new AccelerateDecelerateInterpolator());
        set.playTogether(translate, alpha);
        set.start();
    }

如果要使用View animation,也许也可以实现同样的效果,但估计会很难,因为要注意设置View的属性。比如说收起时并不是全hide,而是半折叠状态,就需要在AnimationListener#onAnimationEnd时去设置特殊的位置状态.

使用时候的建议:

  • 属性动画是post layout的,所以所有属性的初始状态就是你在布局中指定的值,动画是以此为基础开始的。

  • 比较难使用的是translationX和translationY属性,它们的定义是相对于left和top的值。或者理解为相对于layout之后的在父布局中的位置的左边和右边。比如:

    arduino 复制代码
    ObjectAnimator.ofFloat(mBar, "translationY", 0, height);

    这个就是进入的动画,一个View从其上头滑入。反过来:

    arduino 复制代码
    ObjectAnimator.ofFloat(mBar, "translationY", height, 0);

    就是滑出。

ValueAnimator

这是属性动画的核心类,其实它很好的诠释了什么是动画,它就是把某个值在duration内,按照插值器指定的方式从一个值变化到另一个值。看到这个类,就可以感知到动画跟View其实一点关系都没有,动画就是一个随时间变化 的数值而已。

java 复制代码
ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

这意思就是让一个浮点数变量,在1秒内,从0,变化 到100。至于这个有什么具体的效果,要看你如何应用这个随时间变化 的浮点数变量,比如用于控制进度,一般情况下都会将变化的数值用于改变View的视觉变幻形态,但并不局限于此,这里只是为了说明这个动画数值可以用于任何地方:

java 复制代码
        animation.addUpdateListener(anim -> {
            float t = (float) anim.getAnimatedValue();
            mStatusPanel.setText(String.format("Temperature: %04.1f", t));
        });

ObjectAnimator

它是ValueAnimator的一个子类,增强了点功能,它的作用是针对 给定的对象,对其指定的某个属性做动画插值,动画计算与前面提到的ValueAnimator是一样的,只不过说它可以对某个对象的指定的属性做计算,并改变这个属性:

java 复制代码
ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

这意思就是说,把动画计算出来的数值应用于一个textView的translationX属性上面。它与下面的代码,用ValueAnimator来实现,是完全等效的:

java 复制代码
ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

由此可见ObjectAnimator就是多做了一层封装,方便来操作而已。需要注意的是,对象的属性必须要有setter和getter,因为这里会用传进来的属性名字用反射去调用,所以必须要有属性对应的settter和getter方法。

ViewPropertyAnimator

因为大多数情况下是对View做动画,所以又封装出了一个专门用于View的属性动画工具,也即ViewPropertyAnimator,可以非常方便进行属性动画。用一个实例就会相当明了。

比如说想对某个View进行位置,用ObjectAnimator,就需要这么写:

java 复制代码
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

但如果用ViewPropertyAnimator就会非常简洁:

java 复制代码
myView.animate().x(50f).y(100f);

AnimatorSet

用于创建组合,前面的例子已经可以看出来它怎么使用的了。当需要同时实现多个变幻时,就可以把多个Animator用AnimatorSet来组合起来。这个类非常的灵活,可以设置不同的时长,延迟和重复。

插值器

插值器(Interpolators)用以调节数值与时间变化 的关系,因为动画是有时长的,是在duration内,从某个数值变化 到另一数值,而具体随时间怎么变,则由插值器决定。默认是线性的,比如250ms,0f到100,那么就是匀速运动。也可以加速的,减速的,先加速后减速,先减速后加速。

android.view.animation内定义了大量的插值器可供使用。

在XML中来声明动画

与布局类似,动画也是支持在XML中来声明的,这样可以减少代码量,加强复用。方式与方法与写代码差不多,只不过是放在了XML里面,如:

xml 复制代码
<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

这就声明了一个AnimatorSet,是一个位移和渐变动画,使用时用AnimatorInflater 来加载load一下就可以了:

java 复制代码
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

支持的根节点有三个,AnimatorSet,ObjectAnimator和ValueAnimator:

  • ValueAnimator - <animator>
  • ObjectAnimator - <objectAnimator>
  • AnimatorSet - <set>

对于XML中使用ValueAnimator也是一样的,定义好,然后加载出来就可以用了,其实跟前面用代码写是一样的:

xml 复制代码
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />
java 复制代码
ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

注意:有一点需要注意的是,因为View Animation也是支持用XML方式来定义的,且是放在了res/anim下面。所以如果使用Property Animation 时要放在res/animator下面,这个一定要注意。

View状态变化动画

从安卓一开始,对于一些View的状态变化就可以设置不同的Drawable,以给用户视觉上的交互 反馈,最常见的比如按扭,常规状态,Focused状态和按压状态,以及Disabled的状态(不可点击)可以设置不同的Drawable(如icon或者颜色等)以告诉用户。这个是叫做StateListDrawable

现如今,也可以针对 View的不同状态设置不同的动画了,通过StateListAnimator来实现,它的语法与前面提到的StateListDrawable类似,亦是通过一个selector,只不过其中的每个item都是animator,而非drawable,比如:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

这意思就是当点击的时候进行缩放动画,把其保存在res/xml/animate_scale.xml,这里需要注意,StateListDrawable是可以直接保存在res/drawable/下面的,但动画毕竟不是drawable,是不可以放在res/drawable下面。

通过android:stateListAnimator添加给指定的View,如:

xml 复制代码
<Button android:stateListAnimator="@xml/animate_scale"
        ... />

如果不在XML中设置,用代码也可以,先用AnimatorInflater,把它加载出来,然后调用View#setStateListAnimator即可。

设计与实现要符合标准

一个不争的事实是,在安卓的早期版本的时候对动画支持并不友好,因此当时很多GUI的设计都是采用水果平台的规范,导致大量的头部app,GUI交互,特别是动画这一块都是尽可能 的去模仿水果平台。

但时代不一样了,现在在谷歌加大了对安桌的支持力度后,特别是当Material Design出来了以后,从Android 5.0 Lollipop开始,伟大的Google就发布了专门针对UED的设计语言Material Design它不再单单是设计规范了,而是一个非常详细的设计语言,具体到Icon怎么画,动画怎么做。那么,安桌的GUI交互设计与实现,就要符合Material Design的规范了,这样不但体验更符合安桌的风格,实现起来也更加的顺手,因为大量的标准库,AndroidX的库和风格主题动画等等都是以Material Design为标准的,开发人猿在实现的时候有更多的资源可以复用,不用再重复的去造轮子。

Anyway,官方的东西我们还是要学习的并尽可能的遵守的,特别是关于Material Design和Animation。

参考资料

原创不易,打赏点赞在看收藏分享 总要有一个吧

相关推荐
编程洪同学2 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
-$_$-2 小时前
【LeetCode 面试经典150题】详细题解之滑动窗口篇
算法·leetcode·面试
DogDaoDao3 小时前
leetcode 面试经典 150 题:矩阵置零
数据结构·c++·leetcode·面试·矩阵·二维数组·矩阵置零
氤氲息4 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee4 小时前
PHP之伪协议
android·开发语言·php
小林爱4 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
Bigger5 小时前
Tauri(三)—— 先搞定窗口配置
前端·app·客户端
时清云5 小时前
【算法】 课程表
前端·算法·面试
小何开发5 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟6 小时前
Android Jetpack LiveData源码解析
android·android jetpack