原文: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() 方法中的代码:

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

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

旋转

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

// 1
val valueAnimator = ValueAnimator.ofFloat(0f, 360f)valueAnimator.addUpdateListener {val value = it.animatedValue as Float// 2rocket.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。在新界面上点击:

以加速度发射

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

// 1
val valueAnimator = ValueAnimator.ofFloat(0f, -screenHeight)
valueAnimator.addUpdateListener {val value = it.animatedValue as Floatrocket.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。点击屏幕看看。

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

因为使用的是 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()。
  2. 设置动画的时长并开始动画。

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

火箭会向之前使用 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。
  2. 设置 repeatCount 属性来指定动画重复的次数。然后设置 repeatMode 指定当动画值达到终值时要怎么处理。稍后会详讲。
  3. 设置时长,开始动画。

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

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

组合动画

对一个 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 Floatrocket.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)。点击屏幕。

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

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

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 Floatrocket?.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 Floatrocket.translationY = valuedoge.translationY = value
}//2
val rotationAnimator = ValueAnimator.ofFloat(0f, 360f)
rotationAnimator.addUpdateListener {val value = it.animatedValue as Floatdoge.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 Floatrocket.translationY = valuedoge.translationY = value
}// 2
animator.addListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator) {// 3Toast.makeText(applicationContext, "Doge took off", Toast.LENGTH_SHORT).show()}override fun onAnimationEnd(animation: Animator) {// 4Toast.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。点击屏幕。看到这些消息了吗?

注意:你还可以在 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 Floatrocket.translationY = valuedoge.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 一样!:]

  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() 中加入:

  // 1val rocketAnimatorSet = AnimatorInflater.loadAnimator(this, R.animator.jump_and_blink) as AnimatorSet// 2rocketAnimatorSet.setTarget(rocket)// 3val dogeAnimatorSet = AnimatorInflater.loadAnimator(this, R.animator.jump_and_blink) as AnimatorSet// 4dogeAnimatorSet.setTarget(doge)// 5val bothAnimatorSet = AnimatorSet()bothAnimatorSet.playTogether(rocketAnimatorSet, dogeAnimatorSet)// 6bothAnimatorSet.duration = BaseAnimationActivity.Companion.DEFAULT_ANIMATION_DURATIONbothAnimatorSet.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 去创建动画。尽请期待:]

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

Android 动画 Kotlin 教程相关推荐

  1. Android 动画详尽教程 [详尽!详尽!]

    确实好久没有发表文章了,最近整理了 Android 动画方面的教程,从一般的动画到 Material design 中涉及到的动画(只要是属于 Android 动画范畴)整个梳理了一边. 其详尽程度难 ...

  2. Android Intents Kotlin 教程

    原文:Android Intents Tutorial with Kotlin 作者:Steve Smith 译者:kmyhy 更新说明:本教程由 Steven Smith 更新为 Kotlin.An ...

  3. Xamarin Android组件篇教程RecylerView动画组件RecylerViewAnimators(1)

    Xamarin Android组件篇教程RecylerView动画组件RecylerViewAnimators(1) RecyclerView是比ListView和GridView更为强大的布局视图, ...

  4. 使用Kotlin的Android TextView –全面教程

    Kotlin is the official programming language for Android apps development. In this tutorial, we'll be ...

  5. kotlin 垂直滚动_在Android的Kotlin中检测点击或滚动

    kotlin 垂直滚动 Build a responsive UI that shows or hides the toolbar in response to user clicks whilst ...

  6. 【收藏向】谷歌技术团队出品,Android开发入门教程,开源分享

    程序员圈子中,python.java等都是热门领域,网络上相关学习资源也比较多,相较之下Android的学习资料就比较少了,并且大多是碎片化的知识,不够系统和全面. 今天就来整理一下Android开发 ...

  7. 超越Android:Kotlin在后端的工作方式

    by Adam Arold 亚当·阿罗德(Adam Arold) 超越Android:Kotlin在后端的工作方式 (Going Beyond Android: how Kotlin works on ...

  8. Kotlin教程(九)泛型

    写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kotlin的同学.系列文章的知识点会以<Kotlin实战>这本书中顺序编写,在 ...

  9. android oppo 权限,OPPO Reno可尝鲜Android Q:教程如下

    原标题:OPPO Reno可尝鲜Android Q:教程如下 5月8日凌晨,Android Q在谷歌I/O开发者大会上正式亮相.在I/O大会现场,谷歌公布了首批Android Q升级名单,其中OPPO ...

  10. 《Android UI基础教程》——1.2节Android 应用程序的基本结构

    本节书摘来自异步社区<Android UI基础教程>一书中的第1章,第1.2节Android 应用程序的基本结构,作者 [美]Jason Ostrander,更多章节内容可以访问云栖社区& ...

最新文章

  1. C++(八)——文件操作
  2. 叶琰:AI压缩技术在追上传统编码技术
  3. CSS单位--px,em,rem,rpx区别
  4. Python元组的操作
  5. rfid在高速公路管理中的应用_RFID亮灯电子标签在仓储管理中的应用
  6. Object-C使用NSLog打印日志
  7. ACM题目————中位数
  8. C++---之Arraylist
  9. 系统设计与任务分配(团队作业)
  10. php 设置页面最大执行时间 set_time_limit max_execution_time
  11. SQL判断字段是否为空,为NULL
  12. linux连接数问题
  13. linux 与mac使用类似telnet 工具
  14. iOS遇到问题小总结
  15. Ubuntu 20:Cadence IC615集成HSPICE
  16. Android微信代扣sdk无法拉起,微信JS-SDK选择图片遇到的坑
  17. 设计模式:设计模式经典总结
  18. 21天Python进阶学习挑战赛
  19. js中浏览器失焦获焦的几种结局方法
  20. Linux 文件夹右下角有锁,解锁

热门文章

  1. 罗技 连点 脚本_拳头与罗技G展开合作 将推出《英雄联盟》主题外设--新闻中心...
  2. 如何理解T检验和P值
  3. 基于K—近邻的车牌号识别小实验
  4. html js点赞功能实现,利用浏览器的JS代码实现QQ空间自动点赞功能
  5. oracle建立图书管理数据库,Oracle数据库设计 图书管理系统
  6. 自适应鲁棒控制(ARC)实例推导(手写超详细)
  7. 安卓导航车机root方法_不破不立,拥抱安卓的全新奥迪A4L到底有多好用?
  8. Web | MIME类型
  9. 经纬度(度十进制分)—— 度分秒 转换
  10. 饥荒联机一直显示正在启动服务器,饥荒联机版启动服务器时间长 | 手游网游页游攻略大全...