为 SwiftUI 组件添加过渡动画效果
了解如何通过 withAnimation、transition 和 Animation 等 SwiftUI 工具,为视图的状态变化和组件的显示与隐藏添加过渡动画,让用户界面更加自然和动感。
在 iOS 开发中,动画是提升用户体验的重要一环。SwiftUI 通过其简洁的声明式语法,让过渡动画变得更加简单易用。
为什么需要使用过渡动画
在 iOS 应用开发中,经常涉及到 UI 组件的变化与切换,例如以下场景:
- 视图的显示与隐藏
- 视图的尺寸调整
- 视图的位置移动等
如果不添加过渡动画,组件的变化就会显得生硬,就像这样:
使用 SwiftUI 框架,我们只需要几行代码即可添加更加自然的过渡效果:
SwiftUI 中的动画组件
SwiftUI 提供了多个用于实现过渡动画的组件,用于控制视图的状态变化,为视图添加流畅的动画效果。
withAnimation
是用来包裹状态变化的函数,确保 SwiftUI 在状态变化时应用动画。它主要用于指定何时应用动画效果,但是不指定具体的动画效果类型。Animation
则定义了具体的动画的类型、节奏和时长,主要用于视图属性变化的动画,例如位置的移动、大小的变化、颜色的改变等。transition
和 Animation 类似,但只作用于视图的显示和隐藏这个过程。
它们协同工作,使得动画体验更加丰富和自然。
在下面的 AnimationView
组件中,我们将逐步添加各种动画效果,以帮助你更好地理解这些功能:
import SwiftUI
struct AnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle Size") {
isExpanded.toggle()
}
Rectangle()
.frame(
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100
)
.foregroundStyle(Color.blue)
}
}
}
// 添加 PreviewProvider 来进行预览
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
不过目前,这个示例还没有任何过渡动画,视图的变化看起来很生硬:
使用 withAnimation 函数添加动画
withAnimation
是一个函数,这个函数的主要目的是让 SwiftUI 在视图的状态发生变化时应用动画效果。它负责告诉 SwiftUI "请为接下来的状态变化应用动画",但它不会决定具体的动画效果。
例如,你可以使用 withAnimation 来包裹 isExpanded
状态的切换:
import SwiftUI
struct AnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle Size") {
withAnimation() {
isExpanded.toggle()
}
}
Rectangle()
.frame(
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100
)
.foregroundStyle(Color.blue)
}
}
}
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
当你使用 withAnimation
时,SwiftUI 会应用一个默认的过渡效果,通常是轻微的淡入淡出或滑动过渡。现在视图的变化更加平滑和自然。
你还可以通过传递不同的 Animation
参数来自定义动画的类型和节奏,接下来我们会进一步讨论这一点。
配合 Animation 对象自定义动画效果
当你使用 withAnimation()
而不传递任何参数时,SwiftUI 会使用默认的动画行为(通常是一个平滑的淡入淡出效果)。如果你修改默认的动画效果,此时就需要使用到 Animation()
。
Animation()
用于创建一个动画对象。这个动画对象描述了动画的时间曲线、节奏、速度等特性。通过 Animation()
,你可以指定动画如何进行,比如是否是匀速变化、是否有弹簧效果,或者动画应该持续多长时间。
使用 Animation 有两种方式,你可以简单的调用它的预设动画类型,也可以自定义(如设置持续时间、延迟、重复等)动画效果。
Animation 预设基础动画
Animation 内置了一些常见的基础动画类型来快速实现简单的动画效果,比如弹簧、匀速、缓慢开始和结束等。
这些预设动画可以通过简单的调用来实现,你不用传递任何的参数即可使用。
withAnimation(Animation.spring) {
// 动画内容
}
这种方式使用了 SwiftUI 的预设动画,例如 spring()
,它会自动生成带有弹簧效果的动画曲线。
其他常见的预设动画还包括:
Animation.easeIn
: 先缓慢加速后匀速。Animation.easeOut
: 先匀速后缓慢减速。Animation.easeInOut
: 缓慢加速并缓慢减速。Animation.linear
: 匀速动画。
试试为我们之前的代码,应用一个 easeInOut 动画效果:
struct AnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle Size") {
withAnimation(Animation.easeInOut) {
isExpanded.toggle()
}
}
Rectangle()
.frame(
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100
)
.foregroundStyle(Color.blue)
}
}
}
Animation 自定义动画参数
如果你需要更精细的控制(比如动画的时长、延迟、重复次数等),Animaiton 也允许你基于预设动画进行进一步定制。
例如,你可以通过指定持续时间和延迟来自定义动画:
withAnimation(Animation.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.5)) {
// 动画内容
}
或者使用 easeInOut
并指定持续时间:
withAnimation(Animation.easeInOut(duration: 2.0)) {
// 动画内容
}
此外,你还可以为动画添加延迟或重复:
withAnimation(Animation.linear(duration: 2.0).delay(1.0)) {
// 动画内容
}
withAnimation(Animation.spring().repeatForever(autoreverses: true)) {
// 动画内容
}
为示例代码添加重复动画:
struct AnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle Size") {
withAnimation(Animation.linear(duration: 1.0).repeatForever(autoreverses: true)) {
isExpanded.toggle()
}
}
Rectangle()
.frame(
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100
)
.foregroundStyle(Color.blue)
}
}
}
何时使用自定义动画参数
许多开发者常常误以为基础动画过于简单,因此倾向于频繁使用自定义动画效果。事实上,恰恰相反,SwiftUI 内置的基础动画是由 Apple 团队精心设计的,经过广泛应用和市场验证,能够满足大多数场景的需求。除非你非常清楚基础动画无法满足具体需求的原因,并且对如何自定义动画参数有深入理解,否则优先使用内置的基础动画往往是最佳选择。
此外,SwiftUI 本身并未提供完全自定义动画曲线的机制(如直接定义贝塞尔曲线),必须基于基础动画进行二次调整。
使用 .transition 修饰器
.transition()
是一个 SwiftUI 修饰器,主要负责视图的出现和消失时的动画效果。例如,当一个视图从不可见状态变为可见,或从可见状态变为不可见时,transition
可以定义这个变化的过渡方式,比如淡入、淡出、滑入、滑出等。
transition
只作用在视图的“出现”和“消失”阶段。
.transition 与 Animation 的区别
.transition 修饰器和 Animation 结构体类似,都是用来定义动画效果,但是动画的触发,都需要搭配 withAnimaiton() 函数来实现。
.transition 和 Animation 比较类似,但是使用场景不一样。
特性 | transition |
animation |
---|---|---|
作用对象 | 视图的“出现”和“消失”过渡 | 视图属性的变化(颜色、大小、位置等) |
触发条件 | 视图从不可见到可见,或从可见到不可见 | 视图属性(如 frame、color、scale)的变化 |
应用时机 | 控制视图进入或退出时的过渡效果 | 控制已经存在的视图属性变化时的动画效果 |
使用场景 | 例如:视图的条件性显示或隐藏(用 if 或 switch ) |
例如:视图的尺寸、颜色、位置等属性的变化 |
添加显示与隐藏过渡动画
我们将实例代码稍作修改,变成控制它的显示和隐藏:
import SwiftUI
struct AnimationView: View {
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle Visibility") {
// 使用 withAnimation 包裹状态变化,触发过渡动画
withAnimation {
isVisible.toggle()
}
}
// 条件语句控制视图的显示与隐藏
if isVisible {
Rectangle()
.frame(width: 200, height: 200)
.foregroundStyle(Color.blue)
}
}
}
}
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
在添加 .transition 之前,是这样的效果:
使用 .transition 添加 slide 效果:
struct AnimationView: View {
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle Visibility") {
// 使用 withAnimation 包裹状态变化,触发过渡动画
withAnimation {
isVisible.toggle()
}
}
// 条件语句控制视图的显示与隐藏
if isVisible {
Rectangle()
.frame(width: 200, height: 200)
.foregroundStyle(Color.blue)
.transition(.slide) // 使用 .slide 过渡效果
}
}
}
}
.transition 内置以下常见过渡效果:
.opacity
:淡入淡出.slide
:滑动.move
:从特定方向移动进入或退出.scale
:放大或缩小
配合使用 .transition
和 Animation
将 transition
和 animation
配合使用,可以同时为视图的出现、消失和属性变化添加动画效果。通常,transition
用于处理视图的增减,而 animation
用于处理已经显示的视图的变化。
import SwiftUI
struct AnimationView: View {
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle Visibility") {
// 使用弹簧动画包裹状态变化,触发过渡动画
withAnimation(Animation.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.5)) {
isVisible.toggle()
}
}
// 条件语句控制视图的显示与隐藏
if isVisible {
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .top, endPoint: .bottom))
.frame(width: 200, height: 200)
.transition(
AnyTransition.scale(scale: 0.1).combined(with: .move(edge: .trailing))
) // 使用 scale 和 move 过渡效果组合
.animation(Animation.easeInOut(duration: 1.0)) // 控制动画节奏
}
}
}
}
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
.animation() 修饰器
withAnimatio() 函数和状态一起工作,这意味着状态发生变化的时候,会立即动画效果,并且执行一次之后立即停止——这在实践中通常用于人工交互触发。
想象一下,假设我们想实现下面这样的效果,使用 withAnimation 你要如何实现呢?
事实上,使用 withAnimation 非常难以实现,因为它是单次触发的。因此,我们需要引入一种新的动画触发方式—— .animation()
为了实现上述效果,你可以这样使用 .animation() :
import SwiftUI
struct DynamicAnimationView: View {
@State private var progress: CGFloat = 0.5
var body: some View {
VStack {
Slider(value: $progress, in: 0...1)
.padding()
Rectangle()
.frame(width: progress * 200, height: 100)
.foregroundColor(.blue)
.animation(.easeInOut, value: progress) // 自动为 progress 的变化应用动画
}
.padding()
}
}
struct DynamicAnimationView_Previews: PreviewProvider {
static var previews: some View {
DynamicAnimationView()
}
}
.animaiton() 会自动监控 value 的变化,并持续的触发动画效果。因此, withAnimation 是视图驱动,.animation()是数据驱动,这是他们最核心的区别。
.animation() 不仅能用于视图属性与状态绑定的场景(就像上面这样),对于普通的场景,仍然适用。我们可以使用 .animation() 重写下面这个代码实现相同的效果:
import SwiftUI
struct AnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle Size") {
withAnimation() {
isExpanded.toggle()
}
}
Rectangle()
.frame(
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100
)
.foregroundStyle(Color.blue)
}
}
}
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
使用 .animation() 后:
import SwiftUI
struct AnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle Size") {
isExpanded.toggle() // 不再需要使用 withAnimation,动画由 animation(_:value:) 自动处理
}
Rectangle()
.frame(
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100
)
.foregroundStyle(Color.blue)
// 使用 animation(_:value:) 自动为 isExpanded 的变化应用动画
.animation(.easeInOut(duration: 0.5), value: isExpanded)
}
}
}
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
我是廖林,希望这篇关于 SwiftUI 动画的指南能够帮助你在 iOS 开发中打造更加流畅和吸引人的用户体验。如果你喜欢我的内容,欢迎订阅我的博客 Code with Ivens,在这里你将找到更多关于 Swift、SwiftUI 以及 iOS 开发的实用教程与技巧。