要点
Easing(缓动函数)——一个数学函数 f(t),将时间的线性进程 t ∈ [0,1] 映射到动画的非线性值。如果动画将对象的位置从 A 变为 B,持续 500 毫秒,则 easing 确定对象在每个时刻如何在这些点之间移动。线性缓动(f(t) = t)意味着恒定速度——对象机械地移动。非线性缓动——自然:对象加速(ease-in)、减速(ease-out)或具有平滑的开始和结束(ease-in-out)。
缓动函数用三次贝塞尔曲线(Cubic Bézier)描述,其中 X 轴是归一化时间,Y 轴是归一化进度。两个控制点(P1、P2)确定曲线的形状。标准曲线由 CSS 规范固定:ease(0.25, 0.1, 0.25, 1.0)、ease-in(0.42, 0.0, 1.0, 1.0)、ease-out(0.0, 0.0, 0.58, 1.0)、ease-in-out(0.42, 0.0, 0.58, 1.0)。相同的值用于 iOS 中的 CAMediaTimingFunction 和 Android 中的 PathInterpolator。根据 Material Design Motion 指南,缓动曲线应与上下文匹配:出现时使用 ease-out(快速开始),消失时使用 ease-in(快速结束)。
在 iOS 中,与缓动相关的主要工作通过 Core Animation 中的 CAMediaTimingFunction 类完成。它接受两个控制点(c1x, c1y, c2x, c2y)形式的三次贝塞尔曲线,并应用于 CAAnimation(CABasicAnimation、CAKeyframeAnimation)。CAMediaTimingFunction 支持 5 个预定义函数:linear、easeIn、easeOut、easeInEaseOut 和 default(等同于 easeInEaseOut)。
CAMediaTimingFunction 在 CALayer 级别工作——动画在单独的进程 render server 中执行,即使在复杂变换下也能保证 60fps。对于 UIView 属性(frame、alpha、transform)的动画,UIKit 使用 UIView.animate 并带参数 options: .curveEaseInOut,它内部也会创建具有相应曲线的 CAMediaTimingFunction。在 iOS 17+ 中,添加了带有 BlockParameters 的 UIViewAnimationCurve.custom 用于自定义贝塞尔曲线——这消除了编写自己的 CAAnimation 的需要。CAMediaTimingFunction 还用于 UIKit Dynamics 和 UIScrollView 中的减速(UIScrollViewDecelerationRate)。
在 SwiftUI 中,缓动通过 Animation 结构体定义,使用 .easeIn、.easeOut、.easeInOut、.spring 和 .interpolatingSpring 修饰符。与 UIKit 中动画是过程式的(beginAnimations/commitAnimations)不同,SwiftUI 使用声明式方法:动画是变化的状态属性,框架自行决定如何动画化过渡。
.timingCurve(c0x, c0y, c1x, c1y) 修饰符直接接受三次贝塞尔曲线的控制点——这是 CAMediaTimingFunction 的完整模拟。SwiftUI Animation 支持预定义曲线作为静态属性:Animation.easeInOut 等同于 ease-in-out(0.42, 0.0, 0.58, 1.0)。对于弹簧动画,使用 Animation.spring(response:dampingFraction:blendDuration),其中 dampingFraction < 1 创建带有阻尼的弹簧效果。在 WWDC 2023 上,Apple 推出了 Animation.keyframes——逐帧动画,每个关键帧带有自定义缓动,可完全控制轨迹。
在 Android 中,缓动通过 PathInterpolator(API 21+)实现——一个接受带有三次贝塞尔曲线的 Path 对象的类。控制点通过 Path.cubicTo(x1, y1, x2, y2, x3, y3) 定义,其中(x1,y1)是第一个控制点,(x2,y2)是第二个,(x3,y3)是终点(始终为 1,1)。如果需要自定义曲线,则必须使用 PathInterpolator;对于标准场景,可使用 AccelerateDecelerateInterpolator(ease-in-out)、AccelerateInterpolator(ease-in)和 DecelerateInterpolator(ease-out)。
在 Jetpack Compose 中,缓动通过 androidx.compose.animation 包中的 Easing 对象定义。FastOutSlowInEasing、LinearOutSlowInEasing、FastOutLinearInEasing 以及通过 CubicBezierEasing 自定义的 Easing 都可用。Compose Easing 应用于 animate*AsState、AnimatedVisibility 和 AnimatedContent。与 View 系统的关键区别:Compose 在 Kotlin 端(而非 RenderNode 中)插值缓动,这为自定义动画提供了更多灵活性,但在 120Hz 显示器上需要优化。
使用自定义 ease-in-out 曲线的图层位置基本动画。CAMediaTimingFunction 接受三次贝塞尔曲线的控制点,定义平滑的运动开始和结束。
import UIKit
func animateLayerPosition() {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = CGPoint(x: 0, y: 0)
animation.toValue = CGPoint(x: 300, y: 400)
animation.duration = 0.8
let easing = CAMediaTimingFunction(
controlPoints: 0.42, 0.0, 0.58, 1.0
)
animation.timingFunction = easing
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
layer.add(animation, forKey: "positionAnimation")
}
控制点 (0.42, 0.0, 0.58, 1.0) 定义标准 ease-in-out 曲线。SwiftUI 使用相同的点用于 Animation.easeInOut。对于 ease-in,使用(0.42, 0.0, 1.0, 1.0);对于 ease-out——使用(0.0, 0.0, 0.58, 1.0)。Core Animation 在 render server 中以 60/120 fps 的频率执行动画。
SwiftUI 中使用自定义曲线和弹簧效果的声明式动画。animation 修饰符绑定到 isAnimated 的变化。
import SwiftUI
struct EasingExampleView: View {
@State private var isAnimated = false
var body: some View {
VStack {
Circle()
.fill(Color.blue)
.frame(width: 60, height: 60)
.offset(x: isAnimated ? 100 : 0, y: 0)
.animation(
.timingCurve(0.42, 0.0, 0.58, 1.0, duration: 0.6),
value: isAnimated
)
Button("Toggle") {
isAnimated.toggle()
}
}
}
}
.timingCurve(c0x, c0y, c1x, c1y, duration) 修饰符在 SwiftUI 中创建自定义缓动动画。value 参数将动画连接到特定状态——当 isAnimated 改变时,SwiftUI 自动应用曲线。对于弹簧动画,使用 .spring(response: 0.4, dampingFraction: 0.6) 代替 timingCurve。
通过 PathInterpolator 在 Android 中等效 ease-in-out。控制点(0.42, 0.0, 0.58, 1.0)定义与 iOS 中相同的曲线。
import android.animation.ObjectAnimator
import android.graphics.Path
import android.view.animation.PathInterpolator
import android.view.View
fun View.fadeInWithEase() {
val path = Path().apply {
cubicTo(0.42f, 0.0f, 0.58f, 1.0f, 1.0f, 1.0f)
}
val interpolator = PathInterpolator(path)
ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 500
interpolator = interpolator
start()
}
}
具有控制点(0.42, 0.0, 0.58, 1.0)的 PathInterpolator 与 iOS 上的 CAMediaTimingFunction ease-in-out 相同。对于跨平台项目,使用相同的控制点——这保证了视觉上相同的动画行为。Material Design 推荐 FastOutSlowInInterpolator(0.4, 0.0, 0.2, 1.0)作为 Android 特定界面的替代方案。
在 Compose 中,缓动通过具有相同控制点的 CubicBezierEasing 定义。结果——元素以透明度和位移平滑出现。
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@Composable
fun EasingComposable() {
var visible by remember { mutableStateOf(false) }
val easing = CubicBezierEasing(0.42f, 0.0f, 0.58f, 1.0f)
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 500, easing = easing)
)
Column {
Button(onClick = { visible = !visible }) {
Text("Toggle")
}
Box(
modifier = Modifier
.size(100.dp)
.graphicsLayer(alpha = alpha)
.background(MaterialTheme.colorScheme.primary)
)
}
}
Compose 中的 CubicBezierEasing 接受相同的 4 个控制点参数。animateFloatAsState 使用给定曲线自动插值 alpha 值。比较:Compose 中的 FastOutSlowInEasing 等同于(0.4, 0.0, 0.2, 1.0)——Material Design 3 的标准曲线。通过 CubicBezierEasing 的自定义曲线为品牌动画提供完全控制。
常见问题
Easing——缓动函数的通用概念,用于 iOS(CAMediaTimingFunction)、Web(CSS ease-in-out)和设计。Interpolator——这个概念在 Android(TimeInterpolator)中的实现。本质上它们是相同的——动画速度的数学曲线。区别仅在于名称和平台 API。在跨平台项目中,术语 easing 被用作通用术语。
iOS 提供带有 5 个预定义函数的 CAMediaTimingFunction:linear、easeIn、easeOut、easeInEaseOut 和 custom(三次贝塞尔曲线)。在 SwiftUI 中,有 Animation.easeIn、Animation.easeOut、Animation.easeInOut、Animation.spring 和 Animation.interpolatingSpring。对于自定义曲线,使用 Animation.timingCurve(c0x, c0y, c1x, c1y, duration:)。
可以,如果设置相同的三次贝塞尔曲线。iOS 中的 CAMediaTimingFunction 和 Android 中的 PathInterpolator 接受三次贝塞尔曲线的控制点(x1, y1, x2, y2)。例如,ease-in-out(0.42, 0.0, 0.58, 1.0)曲线在两个平台上将产生相同的行为。Material Design 使用这种方法进行跨平台动画。
使用 Material Design Motion 指南:对于元素出现——ease-out(快速开始,平滑结束),对于消失——ease-in(快速结束),对于屏幕间移动——ease-in-out。对交互式元素(按钮、卡片)使用弹簧动画。避免 linear——看起来不自然。
卡顿(jank)发生在动画未达到 60fps 时。检查:您是否在 iOS 中使用 CAMediaTimingFunction(而非计时器),在 Android 中使用 PathInterpolator(而非 Handler.post)。主线程中的繁重计算、复杂的图层层次或大型 drawable 可能导致丢帧。在 iOS 中,对复杂图层使用 Core Animation 配合 shouldRasterize;在 Android 中——使用硬件加速和 GPU 渲染。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。