原文:Android Animation Tutorial with Kotlin
作者:Lisa Luo
译者:kmyhy

更新说明:本教程由 Lisa Luo 更新至 Kotlin 和 Android Studio 3.0。原教程作者是 Artem Kholodnyi。

假若没有那些有趣的、漂亮的动画元素,很难想象手机的使用体验会是什么样子。这些动画不仅仅在整个 app 中负责引导用户,而且也丰富了我们的屏幕。

要创建让屏幕对象生动起来的动画看起来像是在制造飞机发动机,但不用害怕!Android 中有几个工具能够在你创建动画时变得轻松。

你将在本教程中学习一些基本的动画工具,同时将小狗通过火箭发送到太空(也可能是月亮),然后让它安全第回到地球上:]

要制作这个动画,你要学习如何:

  • 创建属性动画——这是 Android 中最常用和最简单的动画
  • Android 视图的移动、渐隐
  • 将动画排列成序列或者同时启动它们
  • 动画的重复和取反
  • 修改动画的时间函数

让我们来学做一个火箭科学家吧 :]

预备知识:本教程和动画相关,因此必须熟悉基本的 Android 编程和 Kotlin,Android Studio 和 XML 布局。
如果你是一个 Android 新手,你可以阅读《Beginning Android Development Part One》。

几个动画、几句代码、疾驰的火箭。

开始

动画是很有趣的研究话题!最好的学习制作动画的方式是动手编写代码:]

首先请下载 Rocket Launcher Starter。将它导入到 Android Studio 3.0 Beta 7 及更高版本,在设备上运行项目。你会很快就会发现你将要做些什么了。你的手机上将显示出你将实现的动画的列表。

随便点击一个列表项。

你会看到两张静态图片:一只小狗和火箭,小狗已经准备好接下来的旅程了。现在,所有的画面的是一样的,动画还没有实现呢。

什么是属性动画?

在开始实现第一个动画之前,先来点理论,让你搞清楚魔术后面的伎俩 :]

假设你想将火箭从屏幕底部移动到屏幕顶部,同时火箭应该用 50 ms 的时间到达。

以下示意图显示火箭的位置应该如何变化:

上面的动画是平滑而且不间断的。但是智能机是数字系统,它使用的是不连续的数值。对于它们来说,时间并不是连续的,它是以每一次前进一小点的方式运行的。

动画由多个静止图像构成,也就是所谓的帧构成,它在特定的时间内显示其中一张图像。这个概念和卡通片第一次出现时没有不同,只不过绘图的方式上不同。

连续两帧之间的时间间隔叫做帧刷新时间——对于属性动画而言,这个值默认是 10 ms。

动画和早期的胶片电影的不同之处在于:因为火箭是以恒定速度运动的,你就可以计算出它在任意时间上的位置。

请看下图中显示的 6 个动画帧。注意:

  • 动画一开始,火箭位于屏幕底部。
  • 火箭上移,每帧移动到一定的距离。
  • 动画结束,火箭到达屏幕顶部。

You see six animation frames shown below. Notice that:

摘要:当绘制某一帧时,根据动画进行的时间和帧刷新率来算出火箭的位置。

幸好,你不需要完全自己来计算,因为 ValueAnimator 已经为你进行计算了。:]

要创建动画,你只需要指定要进行动画的属性的开始值和终止值,以及动画进行到的时间。你还需要添加一个监听者,它会在每一帧设置火箭的新位置。

时间插值器

你可能注意到火箭在整个动画过程中是以恒速运动的——这很不符合实情。材料设计建议你用更自然的方式创建生动的动画来吸引用户的注意。

Android 的 animation 框架使用了时间插值器。ValueAnimator 中包含了一个事件插值器——它是一个实现了 TimeInterpolator 接口的对象。时间插值器决定了属性值如何随时间变化。

再来看一眼那张位置在最简单情况下——即线性插值器随时间变化的图:

下面列出线性插值器如何根据时间来改变值:

根据时间的增长,火箭位置以一种恒速或线性的方式进行变化。

动画也可以使用非线性的插值器。例如 AccelerateInterpolator,它很有趣:

它会对输入值进行平方,导致火箭的速度一开始慢然后迅速加速——就像真实的火箭一样!

这就是一开始我们需要学习的理论知识了,现在是时候开始……

你的第一个动画

首先花点时间来熟悉这个项目。com.raywenderlich.rocketlauncher.animationactivities 这个包包含了 BaseAnimationActivity 及其子类。

打开 res/layout 文件夹下的 activity_base_animation.xml 文件。

在 root 节点下,你会看到一个 FrameLayout 包含了两个带图片的 ImageView:一个是 rocket.png,一个是 doge.png。它们的 android:layout_gravity 都被设置为 bottom|center_horizontal,这样图片会位于屏幕底部的中点。

注意:在本教程中,你会进行大量文件打开操作。在 Android Studio 中可以用下列快捷键:

打开任意文件用 command + shift + O ( Mac)或者 Ctrl + Shift + N ( Linux 和 Windows)
打开一个 Kotlin 类用 command + O(Mac) 或者 Ctrl + N(Linux 和 Windows)

在本 app 中,BaseAnimationActivity 是一个其它动画 activity 的父类。

打开 BaseAnimationActivity.kt 看看。在文件头部,是能被所有动画 activity 访问的成员变量:

  • rocket 是包含了火箭图片的视图
  • doge 是包含了狗狗图片的视图
  • frameLayout 是包含了两者的 FrameLayout
  • screenHeight 用于获取屏幕高度

注意火箭和狗狗两个都是 ImageView,但我们将它们声明为 View,因为属性动画对所有 Android 视图都能使用。视图以懒加载的方式声明,因为它们在布局 inflate 并绑定到对应的 Activity 生命周期事件比如 onCreate() 之前都是 null。

看一下 onCreate() 方法中的代码:

// 1super.onCreate(savedInstanceState)setContentView(R.layout.activity_base_animation)// 2rocket = findViewById(R.id.rocket)doge = findViewById(R.id.doge)frameLayout = findViewById(R.id.container)// 3frameLayout.setOnClickListener { onStartAnimation() }

这段代码主要做了什么:

  1. 调用父类的 onCreate() 方法,然后用指定布局文件调用 setContentView() 方法。
  2. 应用 XML 布局文件并绑定 FrameLayout、火箭以及狗狗到对应的视图。
  3. 设置 FrameLayout 的 onClickListener。当用户点击屏幕,会调用 onStartAnimation() 方法,这是一个抽象方法,每个继承了 BaseAnimationActivity 的 activity 都必须实现这个方法。

这段代码会被本教程中所有 activity 所继承。熟悉完它之后,让我们来开始编写自定义的代码。

发射火箭

如果火箭不被发射,狗狗哪也不能去,因此这非常适合作为开始动画,而且它也非常简单。没有想到,火箭科学家其实也这么简单吧?

打开 LaunchRocketValueAnimatorAnimationActivity.kt, 在 onStartAnimation() 中添加代码:

//1val valueAnimator = ValueAnimator.ofFloat(0f, -screenHeight)//2valueAnimator.addUpdateListener {    // 3  val value = it.animatedValue as Float  // 4   rocket.translationY = value}//5valueAnimator.interpolator = LinearInterpolator()valueAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION//6valueAnimator.start()
  1. 调用静态的 onFloat 方法创建一个 ValueAnimator 实例。这个方法接收多个浮点数作为参数,动画对象的某个属性将实时应用这个数值。例如,这个值开始是 0f,结束时是 -screenHeight。Android 的屏幕坐标从左上角开始,因此将火箭的 translationY 坐标从 0 移动到屏幕的负高度——也就从底部运动到了顶部。
  2. 调用 addUpdateListener() 并出传入一个监听器。ValueAnimator 会在每当修改动画值的时候调用该监听器——注意默认间隔 10 ms。
  3. 从 animator 中取出当前值并转换成 float,当前值的类型是 float,因为创建 ValueAnimator 是使用的是 ofFloat。
  4. 修改火箭的 translationY 来改变火箭的位置。
  5. 设置 animator 的时长和插值器。
  6. 开始动画。

Build & run。从列表中选择 Launch a Rocket。你会看到一个新的画面,点击它!

有意思吧?别担心狗狗被留在了原地,马上它就会乘坐着火箭飞到月亮上。

旋转

给火箭来点旋转怎么样?打开 RotateRocketAnimationActivity.kt 在 onStartAnimation() 中加入:

// 1val valueAnimator = ValueAnimator.ofFloat(0f, 360f)valueAnimator.addUpdateListener {  val value = it.animatedValue as Float  // 2  rocket.rotation = value}valueAnimator.interpolator = LinearInterpolator()valueAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONvalueAnimator.start()

发现有什么变化了吗?

  1. 将 valueAnimator 的值修改为从 0f 到 360f,这将导致火箭旋转一个大圈。注意你可以用 0f - 180f 产生一种“调头”的效果。
  2. 将设置 translationY 替换为设置 rotation,这正是我们需要的。

Build& run,选择 Spin a rocket。在新界面上点击:

以加速度发射

打开 AccelerateRocketAnimationActivity.kt 仍然是 onStartAnimation(),添加:

// 1val valueAnimator = ValueAnimator.ofFloat(0f, -screenHeight)valueAnimator.addUpdateListener {  val value = it.animatedValue as Float  rocket.translationY = value}// 2 - Here set your favorite interpolatorvalueAnimator.interpolator = AccelerateInterpolator(1.5f)valueAnimator.duration = BaseAnimationActivity.DEFAULT_ANIMATION_DURATION// 3valueAnimator.start()

上述代码和 LaunchRocketValueAnimationActivity.kt 中的onStartAnimation() 一模一样,除了这句: 即设置 valueAnimator.interpolator 的这一句。

Build & run,选择 Accelerate a rocket。点击屏幕看看。

可怜的狗狗还是没能坐上飞往月球的火箭……可怜的狗狗。再忍耐一下,狗狗!

因为使用的是 AccelerateInterpolator,你会发现火箭点火之后以加速度前进。请随便尝试一下各种插值器。放心,我会等你的!

什么属性可以被动画?

目前,我们对位置和角度进行过动画,但 ValueAnimator 并不关心你将它的值用在什么地方。

你可以告诉 ValueAnimator 去对下列类型的值进行动画:

  • float ,用 ofFloat 方法创建 animator
  • int,用 ofInt 创建 animator
  • 当前两者不能满足使用时,可以用 ofObject——通常用于颜色动画

你可以对 View 的任意属性进行动画。例如:

  • scaleX 和 scaleY – 单独对视图的 x-轴 或者 y-轴 进行缩放。你也可以将两个值设置为同样的值,对视图的 size 进行动画。
  • translationX 和 translationY – 改变视图在屏幕上的位置。
  • alpha – 对透明度进行动画; 0 表示完全透明,1 表示完全不透明。
  • rotation – 旋转视图,参数为度,360 表示顺时针旋转一周。也可以指定负数,例如 -90 表示反时针旋转 1/4 周。
  • rotationX 和 rotationY – 和 rotation 类似,但旋转轴分别为 x-轴 和 y-轴。这两个属性允许进行 3D 旋转。
  • backgroundColor – 设置一个颜色。参数为表示某个颜色的整数,就像 Android 的颜色常量 Color.YELLOW 或者 Color.BLUE。

ObjectAnimator

ObjectAnimator 是一个 ValueAnimator 的子类。如果你需要对单个对象的单个属性进行动画,就可以使用 ObjectAnimator 了。

和 ValueAnimator 不同,ValueAnimator 需要你设置一个监听器对某个值做某些事情,ObjectAnimator 几乎是自动地为你处理好这些事情:]

打开 LaunchRocketObjectAnimatorAnimationActivity.kt 类,编写如下代码:

// 1val objectAnimator = ObjectAnimator.ofFloat(rocket, "translationY", 0f, -screenHeight)// 2objectAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONobjectAnimator.start()

这段代码完成了如下功能:

  1. 创建一个 ObjectAnimator 对象(和 ValueAnimator 一样),只不过多了前两个参数:

    • rocket 是要执行动画的对象
    • 这个对象必须有一个属性,这个属性的名字是你希望进行动画的属性,这里也就是 translationY。因为 rocket 是一个 View 对象,在基类 View 中有一个访问方法 setTranslationY()。
  2. 设置动画的时长并开始动画。

运行项目,选择 Launch a rocket(ObjectAnimator)。点击屏幕。

火箭会向之前使用 ValueAnimator 一样运动,但代码更少:]

注意:ObjecdtAnimator 有一个限制——它不能同时驱动两个对象,你必须创建两个 ObjectAnimator 。
根据你自己的情况以及准备使用的代码量来决定要使用 ObjectAnimator 还是 ValueAnimator。

变化颜色

结合我们的例子,可以尝试一下对颜色进行动画。创建 animator 时,无论是 ofFloat() 还是 ofInt() 都不能使用颜色作为参数。你得使用 ArgbEvaluator。

打开 ColorAnimationActivity.kt 在 onStartAnimation() 添加代码:

//1val objectAnimator = ObjectAnimator.ofObject(  frameLayout,  "backgroundColor",  ArgbEvaluator(),  ContextCompat.getColor(this, R.color.background_from),  ContextCompat.getColor(this, R.color.background_to))// 2objectAnimator.repeatCount = 1objectAnimator.repeatMode = ValueAnimator.REVERSE// 3objectAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONobjectAnimator.start()

在上面的代码中,我们:

  1. 调用 ObjectAnimator.ofObject() 方法并传入以下参数:

    • frameLayout — 要对这个对象的某个属性进行动画
    • “backgroundColor” — 想要动画的属性
    • ArgbEvaluator() — 指定在两个 ARGB(alpha,red,green,blue) 值之间进行插值的方式。
    • 颜色的开始值和终止值 — 你可以用 ComtextCompat.getColor() 从 color.xml 中获取自定义颜色的颜色资源 id。
  2. 设置 repeatCount 属性来指定动画重复的次数。然后设置 repeatMode 指定当动画值达到终值时要怎么处理。稍后会详讲。
  3. 设置时长,开始动画。

Build & run。选择 Background Color,然后点击屏幕。

太帅了!很快找到诀窍了吧。背景色的变化过程如黄油一般顺滑。

组合动画

对一个 View 进行动画固然好,但你一次只能修改一个对象和一个属性。动画显然不仅限于此。

是时候将狗狗送上天了!

AnimatorSet 允许你将多个动画绑定到一起或者放到一个序列里。将你的的第一个动画传递给 play() 方法,这个方法接受一个 Animator 对象作为参数并返回一个 builder。

然后在 builder 上调用这些方法,每个方法都接受一个 Animator 作为参数:

  • with() — 在播放你在 play() 中指定的第一个动画的同时播放传入的这个 Animator。
  • before() — 在此之前播放
  • after() — 在此之后播放

你可以通过这些方法进行链式调用。

打开 LaunchAndSpinAnimatorSetAnimatorActivity.kt 在 onStartAnimation() 中编写代码:

// 1val positionAnimator = ValueAnimator.ofFloat(0f, -screenHeight)// 2positionAnimator.addUpdateListener {  val value = it.animatedValue as Float  rocket.translationY = value}// 3val rotationAnimator = ObjectAnimator.ofFloat(rocket, "rotation", 0f, 180f)// 4val animatorSet = AnimatorSet()// 5animatorSet.play(positionAnimator).with(rotationAnimator)// 6animatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONanimatorSet.start()

在这段代码中,你进行了:

  1. 创建一个新的 ValueAnimator。
  2. 将一个 AnimatorUpdateListener 绑定到这个 ValueAnimator 以便更新火箭的位置。
  3. 创建一个 ObjectAnimator,用第二个动画修改火箭的旋转角度。
  4. 创建一个 AnimatorSet 对象。
  5. 将 positionAnimator 和 rotationAnimator 一起执行。
  6. 和普通的 animator 一样,设置动画时长并调用 start()。

Build & run。选择 Launch an spin(AnimatorSet)。点击屏幕。

狗狗再次被物理法则无视了。

另外还有一个让你驱动同一对象多个属性的工具,这个工具就是……

ViewPropertyAnimator

在动画代码中最爽的莫过于使用 ViewPropertyAnimator 了,你会看到它的使用使得代码更易于读写。

打开 LaunchAndSpinViewPropertyAnimatorAnimationActivity.kt 在 onStartAnimation() 中加入:

rocket.animate()    .translationY(-screenHeight)    .rotationBy(360f)    .setDuration(BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION)    .start()

这里,animate() 返回了一个 ViewPropertyAnimator 实例,你可以对它进行链式调用。

Build & run,选择 Launch and spin (ViewPropertyAnimator),你会看到和上一节一样的动画。

比较一下上一节中使用 AnimatorSet 的代码:

val positionAnimator = ValueAnimator.ofFloat(0f, -screenHeight)positionAnimator.addUpdateListener {  val value = it.animatedValue as Float  rocket?.translationY = value}val rotationAnimator = ObjectAnimator.ofFloat(rocket, "rotation", 0f, 180f)val animatorSet = AnimatorSet()animatorSet.play(positionAnimator).with(rotationAnimator)animatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONanimatorSet.start()

ViewPropertyAnimator 对多个同步动画提供了更好的效率。它减少了不必要的调用,因此多个属性的动画只发生了一次——和每个属性单独驱动相比而言,后者会导致每个属性都有一些无效的操作。

对两个对象的相同属性进行动画

ValueAnimator 有一个好用的特点,就是你可以重用它的动画值,将它用到多个对象中。

打开 FlyWithDogeAnimationActivity.kt 在 onStartAnimation() 加入:

//1val positionAnimator = ValueAnimator.ofFloat(0f, -screenHeight)positionAnimator.addUpdateListener {  val value = it.animatedValue as Float  rocket.translationY = value  doge.translationY = value}//2val rotationAnimator = ValueAnimator.ofFloat(0f, 360f)rotationAnimator.addUpdateListener {  val value = it.animatedValue as Float  doge.rotation = value}//3val animatorSet = AnimatorSet()animatorSet.play(positionAnimator).with(rotationAnimator)animatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONanimatorSet.start()

上述代码中,你创建了 3 个 animator:

  1. positionAnimator — 用于修改狗狗和火箭的位置
  2. rotationAnimator — 选转狗狗
  3. animatorSet — 将前两个 animator 合并

注意你在第一个 animator 中同时设置了两个对象的 translation。

运行 app,选择 Don’t leave Doge behind(Animating two objects)。你应该明白了什么了吧。登月开始了。

动画监听器

动画通常表示某个动作已经发生或者将会发生。通常,在动画结束,都会做点什么动作。

你不用去观察它,也能知道当动画结束时,火箭会在屏幕以外某个地方呆着。如果你不准备让它招着陆或者关闭 activity,你可以将它移除以节省资源。

AnimatorListener — 会在下列事件发生时接收到来自于 animator 的通知:

  • onAnimationStart() — 动画开始时调用
  • onAnimationEnd() — 动画结束时调用
  • onAnimationRepeat() — 动画重复时调用
  • onAnimationCancel() — 动画取消时调用

打开 WithListenerAnimationActivity.kt 在 onStartAnimation() 中添加:

//1val animator = ValueAnimator.ofFloat(0f, -screenHeight)animator.addUpdateListener {  val value = it.animatedValue as Float  rocket.translationY = value  doge.translationY = value}// 2animator.addListener(object : Animator.AnimatorListener {  override fun onAnimationStart(animation: Animator) {    // 3    Toast.makeText(applicationContext, "Doge took off", Toast.LENGTH_SHORT)        .show()  }  override fun onAnimationEnd(animation: Animator) {    // 4    Toast.makeText(applicationContext, "Doge is on the moon", Toast.LENGTH_SHORT)        .show()    finish()  }  override fun onAnimationCancel(animation: Animator) {}  override fun onAnimationRepeat(animation: Animator) {}})// 5animator.duration = 5000Lanimator.start()

上述代码中,除了多了几个监听器方法,与之前并无不同。这里我们做了这些事情:

  1. 创建并配置 animator。我们用 ValueAnimator 同时修改两个对象的位置——用单个 ObjectAnimator 无法做到这点。
  2. 添加 AnimatorListener.
  3. 当动画开始,显示一个 toast。
  4. 结束时显示一个 toast。
  5. 正常启动动画。

运行 app。选择 Animation events。点击屏幕。看到这些消息了吗?

注意:你还可以在 ViewPropertyAnimator 上在调用链的 start() 之前添加一个 setListener :

rocket.animate().setListener(object : Animator.AnimatorListener { // Your action})

此外,可以在 animate() 之后通过 withStartAction(Runnable) 和 withEndAction(Runnable) 来设置 start 事件和 end 事件。它们和使用 AnimatorListener 设置这些事件是一样的。

动画的 Options

Animation 不是黔之驴,只知道前进、停止。它们可以循环、反转、以某个指定时长运行等等。

在 Android 中,你可以用下列方法来调整动画:

  • repeatCount — 指定动画在第一次执行完之后的重复次数。
  • repeatMode — 指定当动画到达结束时做什么
  • duration — 定义动画的整个时长

打开 FlyThereAndBackAnimationActivity.kt 在 onStartAnimation() 中加入:

// 1val animator = ValueAnimator.ofFloat(0f, -screenHeight)animator.addUpdateListener {  val value = it.animatedValue as Float  rocket.translationY = value  doge.translationY = value}// 2animator.repeatMode = ValueAnimator.REVERSE// 3animator.repeatCount = 3// 4animator.duration = 500Lanimator.start()

在这里,你做了:

  1. 创建一个 animator
  2. 设置 repeatMode,可以是以下取值:

    • RESTART — 从头开始动画
    • REVERSE — 每次循环时反转动画

    在这里,我们设置的是 REVERSE,因为我们想让火箭在起飞后又回到原来的起点。像 SpaceX 一样!:]

  3. 往返两次。

  4. 设置 duration 后开始动画。

注意:为什么在 代码 3 的地方将重复次数指定为 3?每次往返需要循环两次,因此设置为循环次数 3 会让狗狗往返地球 2 次:1次用于第一次起飞后的着陆,另外 2 次则分别用于第 2 次的起飞和着陆。你喜欢让狗狗来回折腾几次?自己去试试呗!

运行 app。选择 Fly there and back(Animation Options),新的窗口打开,点击屏幕。

看到火箭像蚂蚱一样蹦跳了吧!伊隆.马斯克,接招!:]

用 XML 声明动画

我们来看教程中最精彩的部份。在最后一节中,你将学习如何在一个地方声明,在多个地方使用——是的,你完全可以毫无困难地重用你的动画。

通过在 XML 中定义动画,你可以在你的代码中随意重用它。在 XML 中定义动画和你构造视图布局有点类似。

在开始项目的 res/animator 中有一个 jump_and_blink.xml 文件。打开这个文件,你会看到:

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"     android:ordering="together">set>

你会用到的 XML 标签包括:

  • set — 相当于 AnimatorSet
  • animator — 相当于 ValueAnimator
  • objectAnimator — 你猜对了,这就是 ObjectAnimator

在 XML 中使用一个 AnimatorSet 时,它里面需要嵌套 ValueAnimator 和 ObjectAnimator 对象,就好比在进行布局时需要将 View 对象嵌套在 ViewGroup 对象(RelativeLayout,LinearLayout等)中一样。

将 jump_and_blink.xml 中的内容修改为:

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"  android:ordering="together">  <objectAnimator    android:propertyName="alpha"    android:duration="1000"    android:repeatCount="1"    android:repeatMode="reverse"    android:interpolator="@android:interpolator/linear"    android:valueFrom="1.0"    android:valueTo="0.0"    android:valueType="floatType"/>  <objectAnimator    android:propertyName="translationY"    android:duration="1000"    android:repeatCount="1"    android:repeatMode="reverse"    android:interpolator="@android:interpolator/bounce"    android:valueFrom="0"    android:valueTo="-500"    android:valueType="floatType"/>set>

这里定义了一个根元素,即 set 标签。它的 ordering 属性要么是 together 要么是 sequential。默认是 together,但为了明确起见,这里还是写明好些。set 标签中有两个子标签,每个都是一个 objectAnimator。

注意 objectAnimator 的如下属性:

  • android:valueFrom 和 android:valueTo — 开始值和终值,和创建一个 ObjectAnimator 一样。
  • android:valueType — 值的类型,要么是 floatType 要么是 intType。
  • android:propertyName — 想要进行动画的属性。
  • android:duration — 时长。
  • android:repeatCount — 相当于 setRepeatCount。
  • android:repeatMode — 相当于 setRepeatMode。
  • android:interpolator — 指定插值器。通常使用 @android:interpolator/ 作为前缀。当你输入开头之后 Android Studio 会在自动完成列表中显示所有有效的插值器。
  • 在这里无需指明目标对象,这是在后面用 Kotlin 代码进行的。

总之,你添加了两个 objectAnimator 到这个 AnimatorSet 中,它们会同时播放。现在看看如何使用它们。

找到 XmlAnimationActivity.kt 在 onStartAnimation() 中加入:

  // 1  val rocketAnimatorSet = AnimatorInflater.loadAnimator(this, R.animator.jump_and_blink) as AnimatorSet  // 2  rocketAnimatorSet.setTarget(rocket)  // 3  val dogeAnimatorSet = AnimatorInflater.loadAnimator(this, R.animator.jump_and_blink) as AnimatorSet  // 4  dogeAnimatorSet.setTarget(doge)  // 5  val bothAnimatorSet = AnimatorSet()  bothAnimatorSet.playTogether(rocketAnimatorSet, dogeAnimatorSet)  // 6  bothAnimatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION  bothAnimatorSet.start()

在上述代码中,你做了这些事情:

  • 首先,加载 AnimatorSet,从文件 R.animator.jump_and_blink 文件中,就如同你往常 inflat 一个布局文件一样。
  • 然后,将火箭作为刚刚加载的 animator 的 target。
  • 再次用同一个文件加载 animator。
  • 在狗狗身上重复同样动作。
  • 创建第 3 个 AnimatorSet,用它同时播放前两个 animator。
  • 设置根 animator 的时长并启动动画。

嘘!只剩下最后一小个地方了:]

Build & run。选择 Jump and blink (Animations in XML)。点击屏幕看看你努力的成果。

你会看到狗狗跳起来,消失,最后又安全返回地面!:]

接下来做什么

在这里下载最终完成项目。

在本教程中,你学习了:

  • 通过 ValueAnimator 和 ObjectAnimator 来创建和使用属性动画。
  • 根据对应的动画和你的需要,创建时间插值器。
  • 对视图的位置、角度和颜色进行动画。
  • 组合动画。
  • 用 XML 来定义动画,并在多个项目中重用。

基本上,你已经领略了 Android 动画的强大。

如果你想学习更多,请阅读 Android 文档中的时间插值器(请看 Known Indirect Subclasses)。如果你对它们不感兴趣,你可以自己动手实现。你也可以设置动画的 Keyframes,让动画更加复杂。

Android 也有其它动画系统,比如 View 动画和 Drawable 动画。当然也可以用 Canvas 和 OpenGL ES API 去创建动画。尽请期待:]

希望你喜欢本教程。请在论坛中留下你的问题、看法和建议。

更多相关文章

  1. Android(安卓)Transition 页面过度动画
  2. android:contentDescription的作用是什么
  3. Android:overridePendingTransition()函数介绍
  4. 属性动画和补间动画
  5. android实现EditText的多行输入框
  6. Android番外篇——XML layout与CSS
  7. 【Android】应用启动画面
  8. 【Android(安卓)界面效果25】android中include标签的使用
  9. Android的布局管理器(上篇)-LinearLayout、TableLayout、FrameLayo

随机推荐

  1. Android(安卓)Jetpack-Paging使用
  2. Android(安卓)HandlerThread
  3. Android: MediaScanner生成thumbnail的算
  4. android练习一之简易浏览器
  5. androdi 9.0 P版本 CTS 常见问题表格
  6. API 25 (Android(安卓)7.1.1 API) widget
  7. Android(安卓)Init Language
  8. android中的多媒体应用camera
  9. Android之SurfaceView学习一
  10. Exploring the world of Android