Android必知必会——动画

2,554 阅读13分钟

概览

在应用开发中,可以通过动画添加视觉提示,向用户通知应用中的动态。当界面状态发生改变时,动画尤其有用。而且动画还为应用增加了优美的外观,使其拥有更高品质的外观和风格。

帧动画

为位图图形(例如图标或插图)添加动画,应使用可绘制资源动画API。通常,这些动画是使用可绘制资源进行静态定义的,但也可以定义运行时的动画行为。

使用AnimationDrawable

为Drawable添加动画效果,通过接连加载一系列可绘制资源创建动画,像交卷一样按顺序播放。

AnimationDrawable使用,有如下两种方式:

  • 动态添加资源

创建AnimationDrawable实例之后,通过调用方法addFrame(Drawable frame,int duration),顺序添加关键帧。

  • 静态资源 存放于res/drawable目录下
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        //仅循环一次,false表示一致循环
        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>

将该静态资源赋予ImageView,在代码中获取,之后开启动画:

val rocketImage = findViewById<ImageView>(R.id.rocket_image).apply {
            setBackgroundResource(R.drawable.test)
            rocketAnimation = background as AnimationDrawable
        }
rocketImage.setOnClickListener({ rocketAnimation.start() })

使用AnimatedVectorDrawable

VectorDrawable是一种无须像素化或进行模糊处理即可缩放的可绘制对象。

通常在三个 XML 文件中定义添加动画效果之后的矢量可绘制对象:

  • 一个VectorDrawable,其中 元素位于 res/drawable/

  • 一个添加动画效果之后的矢量可绘制对象,其中 位于 res/drawable/

  • 一个或多个对象 Animator,其中 元素位于 res/animator/

添加动画效果之后的矢量可绘制对象可以为 <group><path> 元素的属性添加动画效果。<group> 元素定义一组路径或子组,而 <path> 元素定义要绘制的路径。

补间动画——Animation

Andoird3.0开始在android.view.animation包下,基类为Animation。

Animation的子类(STARS)

  • 缩放动画——ScaleAnimation

  • 平移动画——translateAnimation

  • 透明度动画——AlphaAnimation

  • 旋转动画——RotateAnimation

  • 动画集合——AnimationSet

补间动画各属性及含义

  • Animation
方法 xml属性 含义
setInterpolator interpolator 定义用于平滑动画运动的插值器
setFillEnabled fillEnabled 如果fillEnabled为true,则动画将应用fillBefore的值
setFillBefore fillBefore 控件动画结束时是否还原到开始动画前的状态,默认值是true
setFillAfter fillAfter 控件动画结束时是否保持动画最后的状态, 默认值为false, 如果fillEnabled未设置为true且在View上未设置动画,则将fillAfter假定为true
setDuration duration 动画执行时间
setStartOffset startOffset 延迟
setRepeatCount repeatCount 重复次数,0表示不重复,-1表示无限重复
setRepeatMode repeatMode 重复方式:RESTART 重头开始再来一遍;REVERSE 反向来一遍
setZAdjustment zAdjustment 允许在动画期间调整要动画的内容的Z顺序。 默认值是正常,分三种:normal,动画内容保持其当前的Z顺序;top,在动画过程中,被动画化的内容被强制置于所有其他内容之上;bottom,在动画期间,将要动画化的内容强制置于所有其他内容之下
setBackgroundColor background 动画背后的特殊背景,仅用于窗口动画, 只能是颜色,并且只能是黑色, 如果为0(默认值),则没有背景
setDetachWallpaper detachWallpaper true或false,Window动画的特殊选项:如果此窗口位于墙纸的顶部,请不要使用该墙纸设置动画
setShowWallpaper showWallpaper true或false,Window动画的特殊选项:运行此动画时在后面显示墙纸
setHasRoundedCorners hasRoundedCorners true或false,Window动画的特殊选项:窗口是否应具有圆角
  • ScaleAnimation
方法 xml属性 含义
Constructor formXScale 初始X轴缩放比例,1.0表示无变化
Constructor toXScale 结束X轴缩放比例
Constructor formYScale 初始Y轴缩放比例
Constructor toYScale 结束Y轴缩放比例
Constructor pivotX 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
Constructor pivotY 缩放起点Y坐标
  • TranslateAnimation
方法 xml属性 含义
Constructor fromXDelta 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
Constructor toXDelta 结束点X轴坐标
Constructor fromYDelta 起始点Y轴从标
Constructor toYDelta 结束点Y轴坐标
  • AlphaAnimation
方法 xml属性 含义
formAlpha 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明)
toAlpha 动画结束的透明度
  • RotateAnimation
方法 xml属性 含义
Constructor fromDegrees 旋转开始角度,正数代表顺时针,反之以逆时针
Constructor toDegrees 旋转结束角度,正数代表顺时针,反之以逆时针
Constructor pivotX 旋转中心x坐标
Constructor pivotY 旋转中心y坐标
  • AnimationSet
方法 xml属性 含义
Constructor shareInterpolator 多个动画是否共享插值器
setFillBefore fillBefore 同Animation
setFillAfter fillAfter 同Animation
setDuration duration 时间
setStartOffset startOffset 延迟毫秒
setRepeatMode repeatMode 同Animation

插值器——Interpolator

插值器定义动画的变化率。 这允许基本的动画效果(alpha,缩放,平移,旋转)被加速,减速,重复等。

我们可以自定义插值器实现Interpolator接口,或者使用Android系统内置的插值器:

  • AccelerateDecelerateInterpolator

该插值器的变化率在开始和结束时缓慢但在中间会加快。

  • AccelerateInterpolator

该插值器的变化率在开始时较为缓慢,然后会加快。

  • AnticipateInterpolator

该插值器先反向变化,然后再急速正向变化。

  • AnticipateOvershootInterpolator

该插值器先反向变化,再急速正向变化,然后超过定位值,最后返回到最终值。

  • BounceInterpolator

该插值器的变化会跳过结尾处。

  • CycleInterpolator

该插值器的动画会在指定数量的周期内重复。

  • DecelerateInterpolator

该插值器的变化率开始很快,然后减速。

  • LinearInterpolator

该插值器的变化率恒定不变。

  • OvershootInterpolator

该插值器会急速正向变化,再超出最终值,然后返回。

  • PathInterpolator

可以遍历从点(0,0)延伸到(1,1)的路径的插补器。路径上的x坐标是输入值,输出是该点线的y坐标。这意味着路径必须符合函数 y = f(x)。

属性动画——Animator

属性动画概览

属性动画系统是一个强健的框架,用于为几乎任何内容添加动画效果。可以定义一个随时间更改任何对象属性的动画,无论其是否绘制到屏幕上。属性动画会在指定时长内更改属性(对象中的字段)的值。

通过属性动画,可以定义动画的一下特性:

  • 时长,duration。
  • Interpolator,时间插值。
  • repeatMode和repeatCount。
  • AnimatorSet,将动画分成多个逻辑集,可以一起播放、按顺序播放或者在指定的延迟时间后播放。
  • 帧刷新延迟,指定动画帧的刷新频率,默认10毫秒刷新一次。

属性动画工作原理

属性动画是计算动画的结构:

可以看到,在ValueAnimator中有两个重要的角色:

  • TimeInterpolator,时间插值器。

在补间动画中已经了解了时间插值器的作用,不再进行分析。

  • TypeEvaluator,类型估值器。

由于属性动画,可以直接针对某个数据类型(int、float、argb等),也可以针对某个对象的某种属性,那么其类型是不固定,这样就需要在使用属性动画时,提供类型估值器用于定义如何计算正在添加动画效果的属性的值。

TypeEvaluator是一个接口,只有一个抽象方法: public T evaluate(float fraction, T startValue, T endValue); 三个参数依次表示:从起始值到结束值的分数、起始值、结束值 返回结果是当前fraction时,属性具体的值。

属性动画和补间动画的区别

  • 补间动画仅提供为View对象添加动画效果的功能。

  • 补间动画只能对View的部分方面添加动画,如缩放和旋转等,但是无法对背景颜色等添加动画。

  • 补间动画只会在描绘视图的位置进行修改,而不会修改实际的视图本身。例如,对某个按钮进行位移动画,位移后,能够点击按钮的实际位置仍是位移前的位置。

  • 属性动画,可以完全摆脱这些束缚,还可以为任何对象(视图和非视图)的任何属性添加动画效果,并且实际修改的是对象本身。

ValueAnimator的使用

ValueAnimator可以为某种类型的数值添加动画,但与某个对象没有具体关联。

ValueAnimator.ofInt()(ofFloat()、ofArgb()), 内置的int、float、argb数据类型添加动画,设置起始、终点值以及动画时间。

VlaueAnimator.ofObject( typeEvaluator,startPropertyValue,endPropertyValue),为非内置的三种类型的,其他类型数据添加动画。

针对数值的变化,还可以通过addUpdateListener添加监听:

 ValueAnimator.ofObject(...).apply {
        ...
        addUpdateListener { updatedAnimation ->
            textView.translationX = updatedAnimation.animatedValue as Float
        }
        ...
    }

ObjectAnimator的使用

ObjectAnimator则主要跟某个具体的对象相关联,通过动画设定,逐渐更改该对象的某个属性值,再由对象对该属性值的反馈,实现具体的动画效果。

例如:

//关联textView对象,在动画开始后的1s内,更改其translationX的属性值达到100f。
ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
        duration = 1000
        start()
    }

使用ObjectAnimator需要保证:

  • 要添加动画效果的对象属性必须具有 set() 形式的 setter 函数(采用驼峰式大小写形式)。如果当前不存在,那么可以这么做:

如果有权限,可将 setter 方法添加到类中。

使用有权更改的封装容器类,让该封装容器使用有效的 setter 方法接收值并将其转发给原始对象。

改用 ValueAnimator。

  • 如果在 ObjectAnimator 的一个工厂方法中仅为 values... 参数指定了一个值,则系统会假定它是动画的结束值。因此,要添加动画效果的对象属性必须具有用于获取动画起始值的 getter 函数。getter 函数必须采用 get() 形式。

  • 要添加动画效果的属性的 getter(如果需要)和 setter 方法的操作对象必须与为 ObjectAnimator 指定的起始值和结束值的类型相同(参数类型一致)。

  • 根据要添加动画效果的属性或对象,可能需要对视图调用 invalidate() 方法,以强制屏幕使用添加动画效果之后的值重新绘制自身。但是View自身的一些属性,则可以不用考虑这一点(如setAlpha()、setTranslationX()等)。

AnimatorSet的使用

在许多情况下,需要根据一个动画开始或结束的时间来播放另一个动画。借助 Android 系统,可以将动画捆绑到一个 AnimatorSet 中,以便指定是同时播放动画、按顺序播放还是在指定的延迟时间后播放。还可以相互嵌套 AnimatorSet 对象。

例如:

val bouncer = AnimatorSet().apply {
        play(bounceAnim).before(squashAnim1)
        play(squashAnim1).with(squashAnim2)
        play(squashAnim1).with(stretchAnim1)
        play(squashAnim1).with(stretchAnim2)
        play(bounceBackAnim).after(stretchAnim2)
    }
    val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
        duration = 250
    }
    AnimatorSet().apply {
        play(bouncer).before(fadeAnim)
        start()
    }

上面这段代码,执行的动画顺序为:

1.播放bounceAnim。

2.同时播放squashAnim1、squashAnim2、stretchAnim1、stretchAnim2

3.播放bounceBackAnim。

4.播放fadeAnim。

动画监听器

可以使用两种监听器来监听动画播放期间的重要事件:

  • Animator.AnimatorListener

1.onAnimationStart() - 在动画开始播放时调用。

2.onAnimationEnd() - 在动画结束播放时调用。

3.onAnimationRepeat() - 在动画重复播放时调用。

4.onAnimationCancel() - 在动画取消播放时调用。取消的动画也会调用 onAnimationEnd(),无论它们以何种方式结束。

  • ValueAnimator.AnimatorUpdateListener

onAnimationUpdate() - 对动画的每一帧调用。

指定关键帧——Keyframe

Keyframe 对象由时间值对组成,用于在动画的特定时间定义特定的状态。每个关键帧还可以用自己的插值器控制动画在上一关键帧时间和此关键帧时间之间的时间间隔内的行为。

要实例化 Keyframe 对象,必须使用它的任一工厂方法(ofInt()、ofFloat() 或 ofObject())来获取类型合适的 。然后,通过调用 ofKeyframe() 工厂方法来获取 PropertyValuesHolder 对象。获取对象后,可以通过传入 PropertyValuesHolder 对象以及要添加动画效果的对象来获取 Animator。

如下面代码所示:

val kf0 = Keyframe.ofFloat(0f, 0f)
    val kf1 = Keyframe.ofFloat(.5f, 360f)
    val kf2 = Keyframe.ofFloat(1f, 0f)
    val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
    ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
        duration = 5000
    }

使用属性动画来代替补间动画

对于补间动画操作的属性,包含translationX、translationY、rotation、rotationX、rotationY、scale、scaleX、scaleY、pivotX、pivotY、x、y、alpha,在Android3.0中都增加了相应的setter和getter方法。所以可以使用属性动画来完成补间动画,还能使view的状态真正的发生改变,进而消除了补间动画的这一缺点。

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)

使用ViewPropertyAnimator为view的多个属性添加动画

ViewPropertyAnimator 有助于使用单个底层 Animator 对象轻松为 View 的多个属性并行添加动画效果。

例如对myView的x和y同时做动画,可以有这么三种方式:

//方式一
val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
    val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
    AnimatorSet().apply {
        playTogether(animX, animY)
        start()
    }
    
//方式二
val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
    val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
    ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()
    
//方式三
myView.animate().x(50f).y(100f)

StataListAnimator的使用

通过 StateListAnimator 类,可以定义在视图状态更改时运行的 Animator。此对象充当 Animator 对象的封装容器,只要指定的视图状态(例如“按下”或“聚焦”)发生更改,就会调用该动画。

可使用根 <selector> 元素和子 <item> 元素在 XML 资源中定义 StateListAnimator,每个元素都指定一个由 StateListAnimator 类定义的不同视图状态。每个 <item> 都包含一个属性动画集的定义。

下面就是一个StateListAnimator的xml示意:

res/xml/animate_scale.xml

<?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- 按下状态,x和y增长到原来的%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>
        <!-- 默认状态,无按压状态,x和y不变 -->
        <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>

使用:

<Button android:stateListAnimator="@xml/animate_scale"
            ... />
<!-- 在代码中 -->
val scaleAnimator = AnimatorInflater.loadStateListAnimator(context,R.xml.animate_scale)
button.setStateListAnimator(scaleAnimator) 

在XML中声明动画

属性动画支持使用 XML 声明属性动画,而不是以编程方式进行声明。通过在 XML 中定义动画,可以轻松地在多个 Activity 中重复使用动画,还能更轻松地修改动画序列。

xml存放目录:res/animator/

属性动画类与标签的对应关系:

  • ValueAnimator - <animator>

  • ObjectAnimator - <objectAnimator>

  • AnimatorSet - <set>

AnimatorSet的使用示例:

res/animator/property_animator

<set 
//sequentially 顺序播放
//together     同时播放
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>
    
//在代码中加载并使用
(AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
        setTarget(myView)
        start()
    }

ValueAnimator的使用示例:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:valueType="floatType"//相当于ValueAnimator.onFloat()
        android:valueFrom="0f"
        android:valueTo="-100f" />
        
(AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
        addUpdateListener { updatedAnimation ->
            textView.translationX = updatedAnimation.animatedValue as Float
        }
        start()
    }

示例Demo

github.com/crazycoderl…