作者:Lisa Luo

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

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

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

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

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

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

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

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

开始

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_android studio

随便点击一个列表项。

Android studio kotlin 点不进去源码 android kotlin 教程_插值器_02

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

什么是属性动画?

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

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_插值器_03

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

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

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

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

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

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

You see six animation frames shown below. Notice that:

Android studio kotlin 点不进去源码 android kotlin 教程_android studio_04

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

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

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

时间插值器

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

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_插值器_03

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

Android studio kotlin 点不进去源码 android kotlin 教程_kotlin_06

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

Android studio kotlin 点不进去源码 android kotlin 教程_kotlin_07

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

Android studio kotlin 点不进去源码 android kotlin 教程_Android_08

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

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

你的第一个动画

首先花点时间来熟悉这个项目。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() 方法中的代码:

// 1
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_base_animation)

// 2
rocket = findViewById(R.id.rocket)
doge = findViewById(R.id.doge)
frameLayout = findViewById(R.id.container)

// 3
frameLayout.setOnClickListener { onStartAnimation() }

这段代码主要做了什么:

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

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

发射火箭

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

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

//1
val valueAnimator = ValueAnimator.ofFloat(0f, -screenHeight)

//2
valueAnimator.addUpdateListener {
    // 3
  val value = it.animatedValue as Float
  // 4 
  rocket.translationY = value
}

//5
valueAnimator.interpolator = LinearInterpolator()
valueAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION

//6
valueAnimator.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。你会看到一个新的画面,点击它!

Android studio kotlin 点不进去源码 android kotlin 教程_Android_09

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

旋转

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

// 1
val 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_DURATION
valueAnimator.start()

发现有什么变化了吗?

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_动画_10

以加速度发射

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

// 1
val valueAnimator = ValueAnimator.ofFloat(0f, -screenHeight)
valueAnimator.addUpdateListener {
  val value = it.animatedValue as Float
  rocket.translationY = value
}

// 2 - Here set your favorite interpolator
valueAnimator.interpolator = AccelerateInterpolator(1.5f)
valueAnimator.duration = BaseAnimationActivity.DEFAULT_ANIMATION_DURATION

// 3
valueAnimator.start()

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_动画_11

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

因为使用的是 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 类,编写如下代码:

// 1
val objectAnimator = ObjectAnimator.ofFloat(rocket, "translationY", 0f, -screenHeight)

// 2
objectAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION
objectAnimator.start()

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

  1. 创建一个 ObjectAnimator 对象(和 ValueAnimator 一样),只不过多了前两个参数:
  • rocket 是要执行动画的对象
  • 这个对象必须有一个属性,这个属性的名字是你希望进行动画的属性,这里也就是 translationY。因为 rocket 是一个 View 对象,在基类 View 中有一个访问方法 setTranslationY()。
  1. 设置动画的时长并开始动画。

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

Android studio kotlin 点不进去源码 android kotlin 教程_Android_09

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

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

变化颜色

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

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

//1
val objectAnimator = ObjectAnimator.ofObject(
  frameLayout,
  "backgroundColor",
  ArgbEvaluator(),
  ContextCompat.getColor(this, R.color.background_from),
  ContextCompat.getColor(this, R.color.background_to)
)

// 2
objectAnimator.repeatCount = 1
objectAnimator.repeatMode = ValueAnimator.REVERSE

// 3
objectAnimator.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION
objectAnimator.start()

在上面的代码中,我们:

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_Android_13

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

组合动画

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

是时候将狗狗送上天了!

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

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

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

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

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

// 1
val positionAnimator = ValueAnimator.ofFloat(0f, -screenHeight)

// 2
positionAnimator.addUpdateListener {
  val value = it.animatedValue as Float
  rocket.translationY = value
}

// 3
val rotationAnimator = ObjectAnimator.ofFloat(rocket, "rotation", 0f, 180f)
// 4
val animatorSet = AnimatorSet()
// 5
animatorSet.play(positionAnimator).with(rotationAnimator)
// 6
animatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION
animatorSet.start()

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

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_android studio_14

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

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

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_DURATION
animatorSet.start()

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

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

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

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

//1
val positionAnimator = ValueAnimator.ofFloat(0f, -screenHeight)
positionAnimator.addUpdateListener {
  val value = it.animatedValue as Float
  rocket.translationY = value
  doge.translationY = value
}

//2
val rotationAnimator = ValueAnimator.ofFloat(0f, 360f)
rotationAnimator.addUpdateListener {
  val value = it.animatedValue as Float
  doge.rotation = value
}

//3
val animatorSet = AnimatorSet()
animatorSet.play(positionAnimator).with(rotationAnimator)
animatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATION
animatorSet.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() 中添加:

//1
val animator = ValueAnimator.ofFloat(0f, -screenHeight)

animator.addUpdateListener {
  val value = it.animatedValue as Float
  rocket.translationY = value
  doge.translationY = value
}

// 2
animator.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) {}
})

// 5
animator.duration = 5000L
animator.start()

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

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_插值器_15

注意:你还可以在 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() 中加入:

// 1
val animator = ValueAnimator.ofFloat(0f, -screenHeight)

animator.addUpdateListener {
  val value = it.animatedValue as Float
  rocket.translationY = value
  doge.translationY = value
}

// 2
animator.repeatMode = ValueAnimator.REVERSE
// 3
animator.repeatCount = 3

// 4
animator.duration = 500L
animator.start()

在这里,你做了:

  1. 创建一个 animator
  2. 设置 repeatMode,可以是以下取值:
  • RESTART — 从头开始动画
  • REVERSE — 每次循环时反转动画

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

  1. 往返两次。
  2. 设置 duration 后开始动画。

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

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

Android studio kotlin 点不进去源码 android kotlin 教程_Android_16

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

用 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)。点击屏幕看看你努力的成果。

Android studio kotlin 点不进去源码 android kotlin 教程_插值器_17

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

接下来做什么

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

在本教程中,你学习了:

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

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

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

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