transition #
什么是 transition
?
#
在 SwiftUI 中,transition
是一个视图修饰符,用于定义视图在进入或退出布局时的动画效果。通过 transition
,你可以为视图的出现(Appear)和消失(Disappear)设置一些平滑过渡。
关键特点 #
transition
本质上是一种与视图生命周期(显示/隐藏)绑定的动画。- 它为视图的进入(插入)和退出(删除)提供了动态效果。
- 常见的过渡效果包括淡入淡出、移动、缩放、或者自定义效果。
1. 基本用法 #
显示和隐藏视图 #
你需要结合 @State
和 transition
配合使用,并用 withAnimation
为过渡增加动画效果。
示例:淡入淡出过渡
import SwiftUI
struct ContentView: View {
@State private var isVisible = false // 控制视图显示和隐藏
var body: some View {
VStack {
Button("Toggle View") {
withAnimation { // 添加动画
isVisible.toggle()
}
}
if isVisible {
Text("Hello, World!")
.padding()
.background(Color.blue)
.cornerRadius(10)
.transition(.opacity) // 使用透明度过渡
}
}
}
}
效果:
- 点击按钮时:通过透明度的渐变效果(
opacity
),视图会平滑显现或消失,而不是瞬间改变。
2. 内置过渡效果 #
(1) 常见的过渡类型 #
SwiftUI 提供了多种内置的过渡效果,包括:
过渡类型 | 描述 |
---|---|
.opacity | 透明度渐变,视图通过淡入淡出的透明度动画显示或隐藏。 |
.scale | 缩放过渡,视图通过从小到大或从大到小的缩放动画显示或隐藏。 |
.slide | 滑动过渡,视图从屏幕边缘滑动进入或退出。 |
.move(edge:) | 从某个边缘移动进入或退出(设置边缘参数,如 .leading 、.trailing 、.top 、.bottom )。 |
.offset() | 偏移过渡,视图从指定位置渐渐移动到最终位置,或者离开位置时偏移。 |
.asymmetric() | 定义不同的进入(插入效果)和退出(删除效果)的过渡。 |
(2) 使用 opacity
渐变:
#
.transition(.opacity)
当视图显示或隐藏时,透明度会平滑地从 0
到 1
或从 1
到 0
。
(3) 使用 scale
缩放:
#
.transition(.scale)
当视图显示或隐藏时,视图会从 0x0
缩放到原始大小,或者从原始大小缩小到 0x0
。
(4) 使用 slide
滑动:
#
.transition(.slide)
当视图出现时,它会从屏幕边缘滑动到视图内;当消失时,它会滑出屏幕。
示例:滑动过渡效果
if isVisible {
Text("Hello, SwiftUI!")
.transition(.slide) // 使用 slide 过渡
}
(5) 使用 move(edge:)
:
#
.transition(.move(edge: .leading))
- 当视图插入时,从指定边缘(如
.leading
左边缘)移动到当前布局位置。 - 当视图被移除时,从当前布局位置移到指定边缘。
示例:从顶部移动进入
if isVisible {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 100)
.transition(.move(edge: .top)) // 从顶部进入或退出
}
(6) 组合效果: #
当你想将多个过渡效果组合在一起时,可以使用 .combined(_: Transition)
。
示例:透明度 + 移动组合
.transition(.opacity.combined(with: .move(edge: .bottom)))
- 视图在出现时从底部移动并逐渐显现;
- 视图在消失时透明度降低,并且移回底部。
3. 非对称过渡 #
什么是非对称过渡? #
非对称过渡意味着:视图进入时的过渡效果和视图退出时的过渡效果不一样。
使用 .asymmetric(insert:remove:)
:
insert
参数定义视图插入时的效果。remove
参数定义视图移除时的效果。
示例:非对称移动和透明化过渡
.transition(.asymmetric(insert: .move(edge: .trailing), // 从右侧进入
remove: .opacity)) // 通过透明化消失
完整示例代码:
struct ContentView: View {
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle View") {
withAnimation {
isVisible.toggle()
}
}
if isVisible {
Text("Non-symmetric Transition")
.padding()
.background(Color.orange)
.cornerRadius(10)
.transition(.asymmetric(insert: .slide, remove: .opacity))
}
}
}
}
4. 自定义过渡 #
如何自定义过渡? #
可以通过 .modifier
定义自定义的插入(进入)和移除(退出)效果。
示例:旋转过渡
import SwiftUI
struct RotateTransition: ViewModifier {
let rotation: Double
func body(content: Content) -> some View {
content
.rotationEffect(.degrees(rotation)) // 应用旋转效果
.opacity(rotation == 0 ? 1 : 0) // 控制透明度,消失时透明度为 0
}
}
extension AnyTransition {
static var rotate: AnyTransition {
AnyTransition.modifier(
active: RotateTransition(rotation: 90), // 退出时的效果
identity: RotateTransition(rotation: 0) // 初始情况下(静止时)的效果
)
}
}
struct ContentView: View {
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle Rotation") {
withAnimation {
isVisible.toggle()
}
}
if isVisible {
Text("Rotated Text")
.font(.title)
.padding()
.background(Color.green)
.cornerRadius(10)
.transition(.rotate) // 使用自定义旋转过渡
}
}
.padding()
}
}
效果:
- 视图出现时会有旋转效果。
- 视图消失时旋转退出并逐渐消失。
5. 添加进入和退出动画 #
过渡(transition
)更关注的是「进入/退出」时的效果。而动画更关注的是「整个视图变化过程」。你可以同时结合 transition
和 animation
为视图带来更复杂的动态行为。
示例:结合动画和过渡 #
import SwiftUI
struct ContentView: View {
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle View") {
withAnimation(.easeInOut(duration: 0.5)) {
isVisible.toggle()
}
}
if isVisible {
RoundedRectangle(cornerRadius: 20)
.fill(Color.blue)
.frame(width: 200, height: 100)
.transition(.scale) // 使用缩放动画过渡
}
}
}
}
6. 注意事项 #
只适用于插入/移除的视图:
transition
只作用于通过 条件语句(如if
)或.onAppear/.onDisappear
动态插入的视图。- 如果视图一直存在,并只改变其属性值(不插入/移除),使用
animation
而不是transition
。
过渡范围的限制:
- 过渡动画仅影响从视图「插入或删除」时的行为,而不会影响现有视图的布局调整或样式变化。
添加容器视图以避免冲突:
- 如果给多层嵌套的视图同时应用过渡,可能会有不必要的动画冲突。为每个动画的子视图包裹特定容器是个好方法。
7. 总结 #
transition
是一个用来定义视图插入或移除时动画过渡的修饰符。- 提供了多种内置效果(
opacity
,slide
,move(edge:)
,scale
等),也支持自定义效果。 - 搭配
withAnimation
和if
可视化内容切换。 - 注意在正确的场景下使用
transition
,如视图的动态显示或隐藏,否则需要用animation
。
Animation #
在 SwiftUI 中,动画能够为用户界面带来平滑且自然的动态效果,这不仅能提升用户体验,还能增强应用的交互性。了解动画的体系结构、具体实现方式及核心概念,是将其灵活运用于项目的关键。
接下来,我们从 SwiftUI Animation 的体系结构 入手,详细讲解 Animation 的类型、组成以及使用方式。
SwiftUI 中的 Animation
是一个描述性的数据结构,他定义了动画的行为(例如持续时间、缓动函数等),并将其应用在视图变化上。
动画的构成要素 #
触发动画的条件
- 通过状态变化(State Change)驱动动画。
- 通常绑定到
@State
或其他可以触发视图更新的属性。
动画类型和曲线(Timing Curve)
- 包括动画的缓动效果(如
easeInOut
、linear
)、时长以及重复次数等。
- 包括动画的缓动效果(如
动画修饰符
- 使用
.animation(_:)
或withAnimation(_:)
将动画行为应用到视图或者特定代码逻辑。
- 使用
组合动画(Chained/Composable Animation)
- 多个动画行为可以被组合在一起,用于更复杂的效果。
1. Animation 的类型 #
SwiftUI 提供了多种内置动画类型,主要以时间函数(Timing Function)和缓动曲线(Easing)为核心,控制动画的速度、轨迹和效果。
1.1 基础动画类型 #
动画类型 | 描述 |
---|---|
linear | 线性动画,速度恒定,不受缓动影响。 |
easeIn | 渐入动画,动画开始时速度慢,随后逐渐加速。 |
easeOut | 渐出动画,动画开始时速度快,随后逐渐减速。 |
easeInOut | 渐入渐出动画,开始速度慢,随后加速,再渐渐减速,适合自然切换效果。 |
spring | 弹性动画,产生弹跳和物理感。 |
示例:不同类型动画
import SwiftUI
struct BasicAnimationExample: View {
@State private var isMoved = false
var body: some View {
VStack(spacing: 20) {
Button("Trigger Animation") {
withAnimation(.easeInOut(duration: 1.5)) {
isMoved.toggle()
}
}
// 动画类型:EaseInOut
RoundedRectangle(cornerRadius: 16)
.fill(Color.blue)
.frame(width: 100, height: 100)
.offset(x: isMoved ? 150 : -150, y: 0)
}
}
}
1.2 弹性动画(Spring Animations) #
弹性动画通过模拟物理弹簧的行为带来自然的动感。主要参数包括:
response
:定义弹簧的响应速度,值越小速度越快。dampingFraction
:定义阻尼系数,控制弹跳的幅度(0
是无阻尼,1
是完全阻尼)。blendDuration
:定义动画被打断时的过渡平滑时间。
示例:弹性动画效果
import SwiftUI
struct SpringAnimationExample: View {
@State private var isScaled = false
var body: some View {
Circle()
.fill(Color.red)
.frame(width: 100, height: 100)
.scaleEffect(isScaled ? 1.5 : 1) // 放大和缩小
.onTapGesture {
withAnimation(.spring(response: 0.5, dampingFraction: 0.4)) {
isScaled.toggle()
}
}
}
}
1.3 重复动画(Repeat Animations) #
通过 repeatCount
或 repeatForever
,控制动画的重复次数或无限重复。
示例:无限旋转动画
import SwiftUI
struct RepeatingAnimationExample: View {
@State private var rotation = 0.0
var body: some View {
Image(systemName: "arrow.2.circlepath")
.font(.largeTitle)
.rotationEffect(.degrees(rotation))
.onAppear {
withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
rotation = 360 // 无限旋转
}
}
}
}
属性说明:
repeatForever(autoreverses:)
:是否在每次循环后反向播放。repeatCount(_ count, autoreverses: true)
:设置具体重复次数。
1.4 自定义动画(Custom Timing Curves) #
SwiftUI 支持用贝塞尔曲线模拟时间函数(Timing Curve),通过 timingCurve
创建独特动画曲线。
示例:自定义贝塞尔曲线动画
import SwiftUI
struct CustomAnimationExample: View {
@State private var isMoved = false
var body: some View {
RoundedRectangle(cornerRadius: 25)
.frame(width: 100, height: 100)
.offset(x: isMoved ? 150 : -150)
.onTapGesture {
withAnimation(.timingCurve(0.2, 0.8, 0.4, 1.0, duration: 1.5)) {
isMoved.toggle()
}
}
}
}
2. Animation 的使用方式 #
2.1 修饰符 .animation(_:value:)
#
animation(_:value:)
将动画绑定到视图属性的变化上,当指定的 value
发生变化时,系统会自动触发动画效果。
示例:绑定自动触发动画
struct AnimationBindingExample: View {
@State private var isScaled = false
var body: some View {
Circle()
.fill(Color.purple)
.frame(width: isScaled ? 150 : 50, height: isScaled ? 150 : 50)
.animation(Animation.easeInOut(duration: 1), value: isScaled) // 绑定动画
.onTapGesture { isScaled.toggle() }
}
}
2.2 withAnimation
函数
#
withAnimation
明确为某个代码块定义动画,适合对多个属性同时应用动画。
示例:点击缩放和旋转
struct WithAnimationExample: View {
@State private var isAnimating = false
var body: some View {
RoundedRectangle(cornerRadius: 20)
.fill(Color.orange)
.frame(width: isAnimating ? 200 : 100, height: isAnimating ? 200 : 100)
.rotationEffect(.degrees(isAnimating ? 45 : 0))
.onTapGesture {
withAnimation(.easeInOut(duration: 1.5)) {
isAnimating.toggle()
}
}
}
}
3. 动画的可组合性(Composable Animations) #
SwiftUI 支持对动画进行组合,让多个动画行为在同一时间执行。
3.1 多个动画组合 #
通过不同的动画作用于同一视图的多个属性。
struct ComposableAnimationsExample: View {
@State private var scale: CGFloat = 1
@State private var opacity: Double = 1
var body: some View {
Circle()
.scaleEffect(scale)
.opacity(opacity)
.onTapGesture {
withAnimation(.easeInOut(duration: 1)) { scale = 1.5 }
withAnimation(.linear(duration: 1)) { opacity = 0.5 }
}
}
}
3.2 动画时间线 #
为了实现依次执行不同动画,可以通过状态值切换让动画按时间线进行:
struct SequentialAnimationsExample: View {
@State private var step = 0
var body: some View {
VStack {
Circle()
.offset(x: step >= 1 ? 100 : 0)
.scaleEffect(step >= 2 ? 2 : 1)
.onTapGesture {
withAnimation(Animation.easeOut(duration: 0.5)) { step = 1 }
withAnimation(Animation.easeInOut(duration: 0.5).delay(0.5)) { step = 2 }
}
}
}
}
4. 动画的注意事项 #
使用 State 或绑定驱动动画
动画需要通过可观察的数据发生变化时触发,常见的方式是通过@State
或绑定到Binding
。结合视图生命周期(如 onAppear/onDisappear)触发动画
有些动画可以通过onAppear
自动启动:.onAppear { withAnimation { ... } }
防止不必要的动画
如果某些视图变化不需要动画效果,可以通过手动设置.animation(nil)
禁用动画。优选系统内置动画曲线
系统内置的缓动曲线(如easeIn
和easeOut
)大多符合用户期待,只有在特殊场景时才使用自定义曲线。
总结 #
SwiftUI 的动画体系简单明了,同时又具备高度灵活性,具有如下特点:
- 提供内置的丰富动画类型(
linear
、easeIn
、spring
等)。 - 支持复杂的组合、时间线、顺序和自定义动画。
- 尽量通过状态驱动动画,不再需要复杂的手动管理。
可以根据项目需求选择合适的动画类型和方式,灵活运用动画完成精致的动态体验。
过渡与动画对比 #
在 SwiftUI 中,过渡(Transition) 和 动画(Animation) 是用户界面中常用的两种动效工具。虽然它们都会影响视图的呈现和交互效果,但各自有不同的用途和特性。
下面从核心概念、作用范围、使用场景和注意事项等方面,系统地比较两者的区别。
1. 核心概念 #
动画(Animation) #
动画用来为视图属性的变化提供动态效果,比如平滑移动、缩放、改变颜色或透明度。动画的核心在于让视图的属性(位置、大小、颜色等)在变化过程中平滑过渡。
- 动画 作用于已有视图,对视图的属性传递时间上变化(例如:位置、透明度、大小)。
- 动画本身不会插入或删除视图,但可以让现有视图动态响应属性变化。
例子:扩大视图的尺寸
struct AnimationExample: View {
@State private var isExpanded = false
var body: some View {
RoundedRectangle(cornerRadius: 20)
.frame(width: isExpanded ? 200 : 100, height: isExpanded ? 200 : 100)
.foregroundColor(isExpanded ? .blue : .green)
.onTapGesture {
withAnimation(.easeInOut) {
isExpanded.toggle()
}
}
}
}
在这个例子中:
- 动画作用于
width
和height
的变化,使新尺寸的设置平滑过渡。 - 视图的进入/退出不涉及动画,而只是其外观状态发生改变。
过渡(Transition) #
过渡专门用来处理视图的插入(Appear)与移除(Disappear),规定视图在布局中如何进入或离开。过渡的核心功能是控制视图的显示和隐藏表现。
- 过渡 作用于视图本身的存在与否,定义视图从无到有(或从有到无)的过渡效果。
- 过渡不会修改视图的属性,而是通过一些动态效果(如透明、滑动、缩放等)控制视图插入/移除时的视觉表现。
例子:视图的淡入/淡出
struct TransitionExample: View {
@State private var showView = false
var body: some View {
VStack {
Button("Toggle View") {
withAnimation {
showView.toggle()
}
}
if showView {
RoundedRectangle(cornerRadius: 20)
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.transition(.opacity) // 使用过渡效果
}
}
}
}
在这个例子中:
- 过渡将决定视图进入或消失时是否通过透明度渐变来实现动态效果。
- 没有任何视图属性(如位置或大小)被改变,只有其存在状态被管理。
2. 区别对比 #
特性 | 动画(Animation) | 过渡(Transition) |
---|---|---|
定义 | 为视图的属性变化提供动态效果。 | 为视图的插入与移除提供动态效果。 |
作用范围 | 已经存在的视图,对其属性(如大小、透明度、颜色、位置等)施加动画效果。 | 动态管理视图的显示/隐藏过程。 |
使用时机 | 属性值在视图生命周期中更改(例如点击按钮后改变按钮大小)。 | 视图被加入或移除布局时(例如通过条件判断 if 控制的视图)。 |
触发条件 | 由视图属性的更改触发动画,例如宽度、位置等状态改变。 | 当视图插入或移除父级布局时触发过渡效果。 |
实现方式 | 使用 .animation() 修饰符或 withAnimation 包装代码。 | 使用 .transition() 定义进入/移除的方式,通常与 if/else 或显示状态结合使用。 |
内置效果 | 提供缓动函数(如 .easeInOut 、.linear )控制动画曲线。 | 提供标准过渡效果(如 .opacity 、.move 、.scale ),也可以自定义过渡。 |
是否动态插入视图 | 不处理视图的新建或删除,只处理现有视图属性的动态变化。 | 专注于视图的显示/隐藏(插入/移除)。 |
3. 使用场景 #
动画适用场景(Animation) #
动画更适合在视图已经存在时,为其内部属性的变化创建流畅的视觉效果:
- 视图大小的改变(例如点击按钮后,按钮放大)。
- 视图位置的移动(例如拖拽后视图返回到原位置)。
- 控件的颜色切换(例如切换按钮背景颜色)。
- 圆角、旋转角度、透明度等属性变化动画。
示例:旋转图片 #
struct AnimationExample: View {
@State private var rotation: Double = 0
var body: some View {
Image(systemName: "arrow.right")
.rotationEffect(.degrees(rotation)) // 绑定旋转角度
.onTapGesture {
withAnimation(.easeInOut) {
rotation += 90 // 点击后动画旋转 90 度
}
}
}
}
过渡适用场景(Transition) #
过渡更适合在视图的显示与隐藏时,提供流畅的插入和移除效果:
- 条件触发的视图插入或移除(例如弹出子菜单,关闭模态框)。
- 添加列表中的新元素或移除元素。
- 层级导航或视图堆栈中的视图插入与移除。
示例:显示和隐藏内容 #
struct TransitionExample: View {
@State private var showText: Bool = false
var body: some View {
VStack {
Button("Toggle") {
withAnimation {
showText.toggle() // 控制显示/隐藏
}
}
if showText {
Text("Hello World!")
.transition(.slide) // 滑动过渡插入
.background(Color.blue.opacity(0.3))
}
}
}
}
4. 动画和过渡的结合使用 #
在一个功能中,通常需要结合动画和过渡来实现更复杂的动态效果:
示例:滑入的视图同时修改透明度 #
struct CombinedExample: View {
@State private var showBox = false
var body: some View {
VStack {
Button("Toggle View") {
withAnimation { showBox.toggle() }
}
if showBox {
RoundedRectangle(cornerRadius: 20)
.fill(Color.blue)
.frame(width: 150, height: 150)
.transition(.opacity.combined(with: .slide)) // 淡入 + 滑动过渡
.animation(.easeInOut(duration: 0.5)) // 加强动态效果
}
}
}
}
这段代码组合了 transition
和 animation
:
- 过渡:定义了视图淡入(
opacity
)和从屏幕边缘滑入(slide
)的效果。 - 动画:增加了过渡的曲线和时间指定。
5. 注意事项和总结 #
动画的注意事项 #
- 动画仅作用于 已有视图的属性变化,不能插入或移除视图。
- 当动画作用多个视图时,可能需要使用
ZStack
或其他容器来分层管理。
过渡的注意事项 #
- 过渡只负责视图的进入与退出,不会改变视图处于存在状态时的行为。
- 过渡需要与
withAnimation
搭配使用,否则动画不会生效。 - 过渡需要动态内容控制,如
if
或.remove
.
总结 #
动画(Animation) | 过渡(Transition) | |
---|---|---|
作用对象 | 视图的属性变化 | 视图的插入和移除 |
触发时机 | 属性变化时触发 | 视图从布局插入或移除时触发 |
实现方式 | .animation() 或 withAnimation | .transition() |
适用场景 | 已有视图的平滑动态效果(如移动、缩放) | 条件控制的显示/隐藏的动态效果 |
两者可以协同工作,用于实现复杂的动效,比如内容的动态切换,与动画曲线完美结合。