基于物理特性的动作是依靠力来驱动。弹簧弹力就是这种引导互动和运动的力。弹簧弹力具有以下属性:阻尼和刚度。在基于弹簧的动画中,值和速度是根据施加到每一帧的弹簧弹力计算得出的。
如果您希望应用的动画仅在一个方向减速,不妨考虑改用基于摩擦的快滑动画。
弹簧动画的生命周期
在基于弹簧的动画中,您可以使用 SpringForce
类自定义弹簧的刚度、阻尼比及其最终位置。动画一开始,弹簧弹力就会更新每一帧的动画值和速度。动画会一直持续,直到弹簧弹力达到平衡状态。
例如,如果您在屏幕上拖动某个应用图标,然后通过在图标上松开手指来释放它,那么该图标就会被一种看不见但熟悉的力量拉回其原始位置。
图 1 演示了类似的弹簧效果。圆圈中间的加号 (+) 表示通过轻触手势施加的力。

制作弹簧动画
为应用构建弹簧动画的一般步骤如下:
- 添加支持库:您必须将支持库添加到项目中,才能使用弹簧动画类。
- 制作弹簧动画:主要步骤是创建
SpringAnimation
类的实例,并设置动作行为参数。 - (可选)注册监听器:注册监听器以监控动画生命周期的变化和动画值的更新。
注意:只有在需要按帧更新动画值更改时,才应注册更新监听器。更新监听器可防止动画在单独的线程上潜在地运行。
- (可选)移除监听器:移除不再使用的监听器。
- (可选)设置起始值:自定义动画的起始值。
- (可选)设置值范围:设置动画值范围,将值限制在最小值和最大值范围内。
- (可选)设置起始速度:设置动画的起始速度。
- (可选)设置弹簧属性:设置弹簧的阻尼比和刚度。
- (可选)创建自定义弹簧:如果您不打算使用默认弹簧,或者想在整个动画中使用通用的弹簧,请创建自定义弹簧。
- 开始播放动画:启动弹簧动画。
- (可选)取消动画:在用户突然退出应用或视图变得不可见时取消动画。
下面几部分详细介绍了制作弹簧动画的一般步骤。
添加支持库
如需使用基于物理特性的支持库,您必须按如下方式将支持库添加到项目中:
- 打开应用模块的
build.gradle
文件。 将支持库添加到
dependencies
部分。Groovy
dependencies { def dynamicanimation_version = '1.0.0' implementation "androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version" }
Kotlin
dependencies { val dynamicanimation_version = "1.0.0" implementation("androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version") }
要查看此库的当前版本,请参阅版本页面上有关 Dynamicanimation 的信息。
创建弹簧动画
借助 SpringAnimation
类,您可以为对象制作弹簧动画。如需制作弹簧动画,您需要创建 SpringAnimation
类的实例,并提供一个对象、一个为其添加动画效果的对象属性,以及您希望动画停留的最终弹簧位置(可选)。
注意:制作弹簧动画时,弹簧的最终位置是可选的。不过,必须在启动动画之前定义它。
Kotlin
val springAnim = findViewById<View>(R.id.imageView).let { img -> // Setting up a spring animation to animate the view’s translationY property with the final // spring position at 0. SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f) }
Java
final View img = findViewById(R.id.imageView); // Setting up a spring animation to animate the view’s translationY property with the final // spring position at 0. final SpringAnimation springAnim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0);
基于弹簧的动画可通过更改视图对象中的实际属性来为屏幕上的视图添加动画效果。系统中提供了以下视图:
ALPHA
:表示视图的 Alpha 透明度。默认值为 1(不透明),值为 0 表示完全透明(不可见)。TRANSLATION_X
、TRANSLATION_Y
和TRANSLATION_Z
:这些属性用于控制视图所在的位置,以相对于左侧坐标、顶部坐标和高度的增量(由其布局容器设置)控制视图的位置。TRANSLATION_X
表示左侧坐标。TRANSLATION_Y
表示顶部坐标。TRANSLATION_Z
表示视图相对于其高度的深度。
ROTATION
、ROTATION_X
和ROTATION_Y
:这些属性用于控制围绕轴心点的 2D(rotation
属性)和 3D 旋转。SCROLL_X
和SCROLL_Y
:这些属性表示来源左侧边缘和顶部边缘的滚动偏移量(以像素为单位)。它也以页面滚动距离的形式表示位置。SCALE_X
和SCALE_Y
:这些属性用于控制视图围绕其轴心点进行的 2D 缩放。X
、Y
和Z
:这些是基本的实用程序属性,用于描述视图在其容器中的最终位置。X
是左值与TRANSLATION_X
的总和。Y
是顶部值与TRANSLATION_Y
的总和。Z
是海拔值与TRANSLATION_Z
的总和。
注册监听器
DynamicAnimation
类提供了两个监听器:OnAnimationUpdateListener
和 OnAnimationEndListener
。
这些监听器会监听动画中的更新,例如当动画值发生变化和动画结束时。
OnAnimationUpdateListener
如果您想为多个视图添加动画效果以制作链接的动画,可以将 OnAnimationUpdateListener
设置为在每次当前视图的属性发生变化时接收回调。该回调会根据当前视图属性中发生的变化,通知另一个视图更新其弹簧位置。如需注册监听器,请执行以下步骤:
-
调用
addUpdateListener()
方法并将监听器附加到动画。注意:您需要在动画开始播放前注册更新监听器。不过,只有在需要逐帧更新动画值更改时,才应注册更新监听器。更新监听器可防止动画可能在单独的线程上运行。
-
替换
onAnimationUpdate()
方法,以将当前对象发生的更改告知调用方。以下示例代码说明了OnAnimationUpdateListener
的整体用法。
Kotlin
// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties val (anim1X, anim1Y) = findViewById<View>(R.id.view1).let { view1 -> SpringAnimation(view1, DynamicAnimation.TRANSLATION_X) to SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y) } val (anim2X, anim2Y) = findViewById<View>(R.id.view2).let { view2 -> SpringAnimation(view2, DynamicAnimation.TRANSLATION_X) to SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y) } // Registering the update listener anim1X.addUpdateListener { _, value, _ -> // Overriding the method to notify view2 about the change in the view1’s property. anim2X.animateToFinalPosition(value) } anim1Y.addUpdateListener { _, value, _ -> anim2Y.animateToFinalPosition(value) }
Java
// Creating two views to demonstrate the registration of the update listener. final View view1 = findViewById(R.id.view1); final View view2 = findViewById(R.id.view2); // Setting up a spring animation to animate the view1 and view2 translationX and translationY properties final SpringAnimation anim1X = new SpringAnimation(view1, DynamicAnimation.TRANSLATION_X); final SpringAnimation anim1Y = new SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y); final SpringAnimation anim2X = new SpringAnimation(view2, DynamicAnimation.TRANSLATION_X); final SpringAnimation anim2Y = new SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y); // Registering the update listener anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() { // Overriding the method to notify view2 about the change in the view1’s property. @Override public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value, float velocity) { anim2X.animateToFinalPosition(value); } }); anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() { @Override public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value, float velocity) { anim2Y.animateToFinalPosition(value); } });
OnAnimationEndListener
OnAnimationEndListener
用于通知动画结束。您可以将监听器设置为每当动画达到平衡状态或动画被取消时接收回调。如需注册监听器,请执行以下步骤:
-
调用
addEndListener()
方法并将监听器附加到动画。 -
替换
onAnimationEnd()
方法,以便在动画达到平衡状态或被取消时接收通知。
移除监听器
如需停止接收动画更新回调和动画结束回调,请分别调用 removeUpdateListener()
和 removeEndListener()
方法。
设置动画起始值
要设置动画的起始值,请调用 setStartValue()
方法并传递动画的起始值。如果未设置起始值,动画将使用对象属性的当前值作为起始值。
设置动画值的范围
如果要将属性值限制在特定范围内,您可以设置最小和最大动画值。如果您要为具有固有范围(如 alpha 值,从 0 到 1)的属性添加动画效果,这也有助于控制范围。
-
如需设置最小值,请调用
setMinValue()
方法并传递属性的最小值。 -
如需设置最大值,请调用
setMaxValue()
方法并传递属性的最大值。
这两种方法都会返回正在为其设置值的动画。
注意:如果您已设置起始值并定义了动画值范围,请确保起始值在最小值和最大值范围内。
设置起始速度
起始速度定义动画属性在动画开始时更改的速度。默认起始速度设置为 0 像素/秒。您可以使用触摸手势的速度设置速度,也可以使用固定值作为起始速度。如果您选择提供固定值,我们建议您以“dp/秒”为单位定义该值,然后将其转换为以“像素/秒”为单位。以“dp/秒”为单位定义该值可让速度与密度和外形规格无关。如需详细了解如何将值转换为“像素/秒”,请参阅将“dp/秒”转换为“像素/秒”部分。
如需设置速度,请调用 setStartVelocity()
方法并传递速度(以“像素/秒”为单位)。该方法会返回设置了速度的弹簧弹力对象。
注意:使用 GestureDetector.OnGestureListener
或 VelocityTracker
类方法可检索和计算轻触手势的速度。
Kotlin
findViewById<View>(R.id.imageView).also { img -> SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply { … // Compute velocity in the unit pixel/second vt.computeCurrentVelocity(1000) val velocity = vt.yVelocity setStartVelocity(velocity) } }
Java
final View img = findViewById(R.id.imageView); final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y); … // Compute velocity in the unit pixel/second vt.computeCurrentVelocity(1000); float velocity = vt.getYVelocity(); anim.setStartVelocity(velocity);
将“dp/秒”转换为“像素/秒”
弹簧的速度必须以“像素/秒”为单位。如果您选择提供固定值作为起始速度,请以“dp/秒”为单位提供该值,然后将其转换为以“像素/秒”为单位。要进行转换,请使用 TypedValue
类中的 applyDimension()
方法。请参阅以下示例代码:
Kotlin
val pixelPerSecond: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, resources.displayMetrics)
Java
float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics());
设置弹簧属性
SpringForce
类为每个弹簧属性(例如阻尼比和刚度)定义了 getter 和 setter 方法。要设置弹簧属性,请务必检索弹簧弹力对象或创建可设置属性的自定义弹簧弹力。如需详细了解如何创建自定义弹簧弹力,请参阅创建自定义弹簧弹力部分。
提示:使用 setter 方法时,您可以创建一个方法链,因为所有 setter 方法都会返回弹簧弹力对象。
阻尼比
阻尼比用于描述弹簧振动逐渐衰减的状况。通过使用阻尼比,您可以定义振荡从一次弹跳到下一次弹跳的衰减速度。您可以通过以下 4 种不同的方式阻尼弹簧:
- 当阻尼比大于 1 时,会出现过阻尼现象。可让对象轻轻地返回到静止位置。
- 当阻尼比等于 1 时,会出现临界阻尼现象。它可让对象在最短的时间内返回到静止位置。
- 当阻尼比小于 1 时,会出现欠阻尼现象。它允许对象通过经过静止位置多次过载,然后逐渐到达静止位置。
- 当阻尼比等于零时,便会出现无阻尼现象。它可以让对象永久振动。
如需为弹簧增加阻尼比,请执行以下步骤:
-
调用
getSpring()
方法来检索弹簧,以添加阻尼比。 -
调用
setDampingRatio()
方法并传递要增加到弹簧上的阻尼比。该方法会返回设置了阻尼比的弹簧弹力对象。注意:阻尼比必须为非负数。如果将阻尼比设置为 0,弹簧绝不会到达静止位置。换句话说,它会永久振动。
系统中提供以下阻尼比常量:
DAMPING_RATIO_HIGH_BOUNCY
DAMPING_RATIO_MEDIUM_BOUNCY
DAMPING_RATIO_LOW_BOUNCY
DAMPING_RATIO_NO_BOUNCY
图 2:高弹力
图 3:中弹跳
图 4:低弹跳
图 5:无弹跳
默认阻尼比设置为 DAMPING_RATIO_MEDIUM_BOUNCY
。
Kotlin
findViewById<View>(R.id.imageView).also { img -> SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply { … //Setting the damping ratio to create a low bouncing effect. spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY … } }
Java
final View img = findViewById(R.id.imageView); final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y); … //Setting the damping ratio to create a low bouncing effect. anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); …
刚度
刚度定义了用于测量弹簧强度的弹簧常量。刚度较硬的弹簧可在未处于静止位置时对所连接的对象施加更大的力。如需为弹簧增加刚度,请执行以下步骤:
-
调用
getSpring()
方法来检索弹簧,以增加刚度。 -
调用
setStiffness()
方法并传递要添加到弹簧的刚度值。该方法会返回设置了刚度的弹簧弹力对象。注意:刚度必须为正数。
系统中提供了以下刚度常量:
图 6:高刚度
图 7:中刚度
图 8:低刚度
图 9:非常低的刚度
默认刚度设置为 STIFFNESS_MEDIUM
。
Kotlin
findViewById<View>(R.id.imageView).also { img -> SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply { … //Setting the spring with a low stiffness. spring.stiffness = SpringForce.STIFFNESS_LOW … } }
Java
final View img = findViewById(R.id.imageView); final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y); … //Setting the spring with a low stiffness. anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW); …
创建自定义弹簧弹力
您可以创建自定义弹簧弹力,作为使用默认弹簧弹力的替代方案。通过自定义弹簧弹力,您可以在多个弹簧动画之间共享同一个弹簧弹力实例。创建弹簧弹力后,您可以设置阻尼比和刚度等属性。
-
创建一个
SpringForce
对象。SpringForce force = new SpringForce();
-
通过调用相应的方法来分配属性。您还可以创建方法链。
force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);
-
调用
setSpring()
方法将弹簧设置为动画效果。setSpring(force);
启动动画
可通过以下两种方式启动弹簧动画:调用 start()
或调用 animateToFinalPosition()
方法。这两种方法都需要在主线程上调用。
animateToFinalPosition()
方法会执行两项任务:
- 设置弹簧的最终位置。
- 启动动画(如果尚未启动)。
由于该方法会更新弹簧的最终位置并根据需要启动动画,因此您可以随时调用此方法,以更改动画过程。例如,在链接的弹簧动画中,一个视图的动画依赖于另一个视图。对于此类动画,使用 animateToFinalPosition()
方法更方便。通过在链接的弹簧动画中使用此方法,您无需再担心接下来要更新的动画当前是否正在运行。
图 10 展示了一个链接的弹簧动画,其中一个视图的动画依赖于另一个视图。

如需使用 animateToFinalPosition()
方法,请调用 animateToFinalPosition()
方法并传递弹簧的静止位置。您还可以通过调用 setFinalPosition()
方法来设置弹簧的静止位置。
start()
方法不会立即将属性值设置为起始值。属性值在每次动画脉冲时都会发生变化,这发生在绘制传递之前。
因此,这些更改会反映在下一帧中,就好像值是立即设置的一样。
Kotlin
findViewById<View>(R.id.imageView).also { img -> SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply { … //Starting the animation start() … } }
Java
final View img = findViewById(R.id.imageView); final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y); … //Starting the animation anim.start(); …
取消动画
您可以取消动画或跳至动画结尾处。需要取消或跳至动画结尾的理想情况是用户互动要求立即终止动画。这主要发生在用户突然退出应用或视图变得不可见时。
您可以使用两种方法来终止动画。cancel()
方法会在动画当前所处的值处终止它。skipToEnd()
方法会将动画跳到最终值,然后终止它。
请务必先检查弹簧的状态,然后才能终止动画。如果状态为无阻尼,则动画永远无法到达静止位置。
如需检查弹簧的状态,请调用 canSkipToEnd()
方法。如果弹簧处于阻尼状态,该方法会返回 true
,否则返回 false
。
了解弹簧的状态后,您可以使用 skipToEnd()
方法或 cancel()
方法终止动画。只能在主线程上调用 cancel()
方法。
注意:通常,skipToEnd()
方法会导致视觉上的跳跃。