Android 动画与过渡之属性动画

4,694 阅读16分钟

一 动画与过渡简介

Android开发中,动画需求太常见了,不过因为知识点太多,时不时就遗忘了得重新去找资料,干脆花点时间整理整理哈。

当界面因响应用户操作而发生变化时,你应为布局变化添加动画。这些动画可向用户提供有关操作的反馈,并有助于让用户始终关注界面。

动画就是视觉上的提示,通知用户应用发生了变化。对于动画的设计规范,有兴趣研究的,可以看看Material Design动作指南

图1.1 在对话框出现和消失时采用巧妙的动画效果会让界面变化显得不那么突兀(来自Android官网)

从Android4.4(API 19)开始,Android提供了一套过渡框架,用来快速实现 Activity 或 Fragment 内切换布局时的动画。您只需指定起始和结束布局以及要使用的动画类型。然后,系统会找出并执行这两种布局之间的动画。

注: 在 Android 5.0(API 21)及更高版本中,Android的过渡框架才可以支持在Activity 之间过渡的动画

图1.2 界面过渡效果(来自Android官网)

二 属性动画基础篇

Android动画可分为两大类别:

  1. 视图动画
  2. 属性动画

其中,视图动画又分成两种:

视图动画因实现上的问题,如它只会在绘制视图的位置进行修改,而不会修改实际的视图本身等,已经渐渐淡出视野,这里不作过多讲解,需要可以简单查看下视图动画介绍动画资源或自行查找资料,欢迎补充资料链接哈

2.1 属性动画概述

属性动画是一个非常强健灵活的框架,可以定义随时间而更改的任何属性的动画,简而言之,就是不断更新属性而实现动画的效果。

:属性动画,改变的是属性值,如对于自定义的进度属性progress,它只是改变这个字段的赋值,但不会自动触发重绘机制,需要在自定义的setProgress()方法中自主声明invalidate()

属性动画的上手和进阶,可以直接查看扔物线大神的文章,知识点详细,还配有视频讲解:

动画知识点是比较多的,记住有这么一回事,在实际开放中,需要实现了,有个大致的方向,再回来重温一遍。

大神的文章里,对属性动画讲解得很透彻全面,可以直接查看大神文章。这里只是做了额外的知识记录和内容整理总结,方便个人以后查看回忆。

文章大部分图片来自于Android官网或以上三篇文章

2.2 属性动画特性概述

在属性动画系统里,可以自定义动画的以下通用特性:

  1. 动画时长:指定动画时长,默认300ms
  2. 时间插值器/速度模型:指定如何根据动画的当前已播放时长来推算属性的值
  3. repeatCount和repeatMode:重复次数和重复模型,指定是否在某个时长结束后重复播放动画以及重复播放动画多少次。还可以指定是否要反向播放动画。
  4. 动画监听器:给动画设置监听器,可以在关键时刻得到反馈,从而及时做出合适的操作,例如在动画的属性更新时同步更新其他数据,或者在动画结束后回收资源等。
  5. 使用 TypeEvaluator:自定义实现可做动画的属性类型TypeEvaluator

2.3 属性动画实现方式

Android动画系统提供了Animator的三种类别实现:

类别 说明
ValueAnimator 属性动画的主计时引擎。为属性添加动画效果分为两个步骤:计算添加动画效果之后的值,以及对要添加动画效果的对象和属性设置这些值。ValueAnimator 不会执行第二个步骤,因此,您必须监听由 ValueAnimator 计算的值的更新情况,并使用您自己的逻辑修改要添加动画效果的对象。如需了解详情,请参阅使用 ValueAnimator 添加动画效果部分
ObjectAnimator ValueAnimator 的子类,用于设置目标对象和对象属性以添加动画效果。此类会在计算出动画的新值后相应地更新属性。在大多数情况下,您不妨使用 ,因为它可以极大地简化对目标对象的值添加动画效果这一过程。如需了解详情,请参阅使用 ObjectAnimator 添加动画效果部分
AnimatorSet 此类提供一种动画集合机制,控制多个动画的执行顺序,可以将动画设置为一起播放、按顺序播放或者在指定的延迟时间后播放。如需了解详情,请参阅使用 AnimatorSet 编排多个动画部分
ViewPropertyAnimator 行为表现与ObjectAnamtor非常相似,但使用上更加简便高效易读。如需了解详情,请参阅使用 ViewPropertyAnimator 添加动画效果

这里说明下几种类型之间的关联和区别,ValuesAnimator是动画的核心计算引擎,但因为功能太基础,所以一般使用它的子类ObjectAnimator,而ViewPropertyAnimator则可以理解为ObjectAnimator的便捷版本,方便操作。AnimatorSet则是为了控制多个动画之间的执行顺序,与另外几种是不一样的。更多理解,可以查阅ValueAnimator 最基本的轮子

另外,ViewPropertyAnimator是不需要调用start()方法的,而其他类型是需要的

2.4 通用特性:插值器/速度模型

插值器Interpolator:指定了如何根据时间计算动画中的特定值。例如,您可以指定动画在整个动画中以线性方式播放,即动画在整个播放期间匀速移动;也可以指定动画使用非线性时间,例如动画在开始后加速并在结束前减速。

具体的插值器类型及使用方式可查阅:setInterpolator(Interpolator interpolator) 设置 Interpolator

如果现有的插值器类型满足不了,可以通过实现TimeInterpolator接口,实现自己的插值器。

2.5 通用特性:监听器

2.5.1 监听器类型

(1)Animator.AnimatorListener

  • onAnimationStart() - 在动画开始播放时调用。
  • onAnimationEnd() - 在动画结束播放时调用。

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

  • onAnimationCancel() - 在动画取消播放时调用。

    注意:取消的动画也会调用 onAnimationEnd(),无论它们以何种方式结束。

(2)ValueAnimator.AnimatorUpdateListener

  • onAnimationUpdate() - 对动画的每一帧调用。监听此事件即可使用 ValueAnimator 在动画播放期间生成的计算值。要使用该值,请查询传递到事件中的 ValueAnimator 对象,以使用 getAnimatedValue() 方法获取当前添加动画效果之后的值。如果使用了 ValueAnimator,则必须实现此监听器。

(3)Animator.AnimatorPauseListener

  • onAnimationPause(Animator animation) - 在动画暂停时调用。
  • onAnimationResume(Animator animation) - 在动画恢复时调用。

:如果不需要实现 Animator.AnimatorListener 接口的所有方法,则可以扩展 AnimatorListenerAdapter 类,而非实现接口。AnimatorListenerAdapter 类继承了Animator.AnimatorPauseListenerAnimator.AnimatorListener接口,并提供了空实现。可以自行根据需求重写相对应方法。

2.5.2 设置监听器

ViewPropertyAnimatorObjectAnimator 略微不一样: ViewPropertyAnimator 用的是 setListener()setUpdateListener() 方法,可以设置一个监听器,要移除监听器时通过 set[Update]Listener(null) 填 null 值来移除;而 ObjectAnimator 则是用 addListener()addUpdateListener() 来添加一个或多个监听器,移除监听器则是通过 remove[Update]Listener() 来指定移除对象。

另外的不同之处是:

  1. 由于 ObjectAnimator 支持使用 pause() 方法暂停,所以它还多了一个 addPauseListener() / removePauseListener() 的支持;
  2. ViewPropertyAnimator 则独有 withStartAction()withEndAction() 方法,可以设置一次性的动画开始或结束的监听。

针对ViewPropertyAnimator.withStartAction/EndAction(),与Animator.AnimatorListeneronAnimationStart() / onAnimationEnd() 相比不同的地方主要有两点:

  1. withStartAction() / withEndAction()一次性的,在动画执行结束后就自动弃掉了,就算之后再重用 ViewPropertyAnimator 来做别的动画,用它们设置的回调也不会再被调用。而 set/addListener() 所设置的 AnimatorListener 是持续有效的,当动画重复执行时,回调总会被调用。

  2. withEndAction() 设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和 AnimatorListener.onAnimationEnd() 的行为是不一致的。

三 属性动画进阶篇

3.1 针对特殊的属性来做属性动画:TypeEvaluator

如果要为 Android 系统无法识别的类型添加动画效果,则可以通过实现 TypeEvaluator 接口来创建自己的识别方式。Android 系统本身可以识别的类型为 intfloat 或颜色,实际上也是分别由 IntEvaluatorFloatEvaluatorArgbEvaluator 类来提供识别支持。

具体实现可以查看自定义 Evaluator

img
img

颜色属性动画

3.2 同一个动画中改变多个属性:PropertyValuesHolder

以下内容转自:PropertyValuesHolder 同一个动画中改变多个属性

很多时候,你在同一个动画中会需要改变多个属性,例如在改变透明度的同时改变尺寸。如果使用 ViewPropertyAnimator,你可以直接用连写的方式来在一个动画中同时改变多个属性:

view.animate()
        .scaleX(1)
        .scaleY(1)
        .alpha(1)
img
img

而对于 ObjectAnimator,是不能这么用的。不过你可以使用 PropertyValuesHolder 来同时在一个动画中改变多个属性。

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX"1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY"1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha"1);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();

PropertyValuesHolder 的意思从名字可以看出来,它是一个属性值的批量存放地。所以你如果有多个属性需要修改,可以把它们放在不同的 PropertyValuesHolder 中,然后使用 ofPropertyValuesHolder() 统一放进 Animator。这样你就不用为每个属性单独创建一个 Animator 分别执行了。

3.3 多个动画配合执行:AnimatorSet

以下内容转自:AnimatorSet 多个动画配合执行

前面提过,AnimatorSet就是控制多个动画执行关系的一个类,比如,在内容的大小从 0 放大到 100% 大小后开始移动。这种情况使用 PropertyValuesHolder 是不行的,因为这些属性如果放在同一个动画中,需要共享动画的开始时间、结束时间、Interpolator 等等一系列的设定,这样就不能有先后次序地执行动画了。

这就需要用到 AnimatorSet 了。

ObjectAnimator animator1 = ObjectAnimator.ofFloat(...);
animator1.setInterpolator(new LinearInterpolator());
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
animator2.setInterpolator(new DecelerateInterpolator());

AnimatorSet animatorSet = new AnimatorSet();
// 两个动画依次执行
animatorSet.playSequentially(animator1, animator2);
animatorSet.start();
img
img

使用 playSequentially(),就可以让两个动画依次播放,而不用为它们设置监听器来手动为他们监管协作。

AnimatorSet 还可以这么用:

// 两个动画同时执行
animatorSet.playTogether(animator1, animator2);
animatorSet.start();

以及这么用:

// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB)
// 的方式来精确配置各个 Animator 之间的关系
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();

有了 AnimatorSet ,你就可以对多个 Animator 进行统一规划和管理,让它们按照要求的顺序来工作。

3.4 把同一个属性拆分:PropertyValuesHolders.ofKeyframe()

以下内容转自:PropertyValuesHolders.ofKeyframe() 把同一个属性拆分

除了合并多个属性和调配多个动画,你还可以在 PropertyValuesHolder 的基础上更进一步,通过设置 Keyframe (关键帧),把同一个动画属性拆分成多个阶段。例如,你可以让一个进度增加到 100% 后再「反弹」回来。

// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(00);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(180);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();
img
img

四 通过XML声明属性动画

4.1 在xml中声明动画

属性动画系统支持使用 XML 声明属性动画。通过在 XML 中定义动画,可以轻松地在多个 Activity 中重复使用动画,还能简便地修改动画执行顺序。

为了与旧版视图动画框架的动画文件区分开来,从 Android 3.1 开始,应将属性动画的 XML 文件保存到 res/animator/ 目录中

属性动画类型,对应的xml标签如下:

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

要查找 XML 声明中可使用的属性细节,可以查阅动画资源

以下例子,先将透明度设为1,然后依次播放两组对象动画,其中第一个嵌套集会同时播放两个对象动画:

 <set android:ordering="sequentially">
      <objectAnimator
            android:valueFrom="0.0"
            android:valueTo="1.0"
            android:propertyName="alpha"
            android:duration="0" />
        <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>

将动画加载到具体view中,需要先加载xml资源为AnimatorSet对象,然后调用setTarget()方法来设置目标对象,并调用start()方法即可。

  (AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
        setTarget(myObject)
        start()
    }

也可以在 XML 中声明 ValueAnimator

 <animator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:valueType="floatType"
        android:valueFrom="0f"
        android:valueTo="-100f" />

同样的,也是先加载成 ValueAnimator对象,然后按 ValueAnimator的使用方法,添加 AnimatorUpdateListener、获取更新后的动画值并在某个视图的属性中使用它:

 (AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
        addUpdateListener { updatedAnimation ->
            textView.translationX = updatedAnimation.animatedValue as Float
        }

        start()
    }
    

4.2 使用 StateListAnimator 为视图状态更改添加动画效果

Drawable资源的根 <selector>一样,Android动画系统也提供了StateListAnimator,可以定义在视图状态更改时运行的 Animator。如当view被按下时放大,松开时恢复原状。

文件需存放到res/xml/目录,同样也是用根 <selector> 元素包裹,用子 <item> 元素定义状态,以下创建一个状态Animator,在按下后放大视图的 x 和 y 比例,松开后恢复原状:

    <?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>

StateListAnimator 应用到view中,需要添加 **android:stateListAnimator**属性:

 <Button android:stateListAnimator="@xml/animate_scale"
            ... />

如果想通过代码将StateListAnimator 分配给view,则可使用 AnimatorInflater.loadStateListAnimator() 方法,然后使用 View.setStateListAnimator() 方法将 Animator 分配给view

五 参考链接

  1. Animations and Transitions

  2. HenCoder Android 自定义 View 1-6: 属性动画(上手篇)

  3. 【HenCoder Android 开发进阶】自定义 View 1-7:属性动画(进阶篇)

  4. HenCoder Android 自定义 View 1-8 硬件加速

六 ❤️ 感谢

  1. 如果觉得这篇内容对你有所帮忙,点赞支持下(👍)

  2. 关于纠错和建议:欢迎直接在留言分享记录(🌹)

- END -