你曾在 Keynote 使用过瞬间移动动画( magic move animation )吗?藉由瞬间移动效果, 你可以轻松建立投影片间的平滑动画( slick animation )。Keynote 会自动分析两张投影片之间的物件,并自动渲染动画。同样,SwiftUI 也将瞬间移动动画带入应用程序开发中, 使用该框架的动画是自动且神奇的。当你定义一个视图的两个状态,SwiftUI 会找出其余的状态,接着以动画呈现两个状态之间的变化。
SwiftUI 使你能够为单个视图的变化以及两个视图之间的转场来设定动画。SwiftUI 框架具有许多的内建动画,可建立不同的效果。
SwiftUI 提供两种动画类型:隐式( implicit )与显式( explicit )。这两个方式可以让你为视图及视图转场设定动画。为了实现隐式动画,SwiftUI 框架提供一个名为 animation
的修饰器,你把这个修饰器加到要设定动画的视图上,并指定喜欢的动画类型。或者,你可以定义动画的持续时间与延迟时间,SwiftUI 会根据视图的状态变化来自动渲染动画。
显式动画对你要显示的动画提供更具局限性的控制,其并非将修饰器加到视图,而是在 withAnimation()
区块中,告诉 SwiftUI 若有什么的状态变化时,要绘制动画。
仍是觉得有些困惑吗?没有关系,藉由几个示例,你就会更有概念了。
我们从隐式动画来开始介绍,我建议你建立一个新项目来看动画的实际效果。你可以任意为项目命名,而我将它命名为 SwiftUIAnimation
。
我们来看图 9.1,这是一个简单的可点击视图,由红色圆形与心形所组成。当使用者点击心形或圆形时,圆形的颜色会变成淡灰色,而心形则会变成红色,同时心形图示的大小也变得较大。因此,以下是其状态变化:
如果你使用SwiftUI 来实现可点击的圆形,以下为程序内容,你可以建立一个 Xcode 的新项目,并将程序插入至 ContentView.swift
来测试看看:
struct ContentView: View {
@State private var circleColorChanged = false
@State private var heartColorChanged = false
@State private var heartSizeChanged = false
var body: some View {
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.onTapGesture {
circleColorChanged.toggle()
heartColorChanged.toggle()
heartSizeChanged.toggle()
}
}
}
我们定义三个状态变量来建立状态模型,初始值皆设定为“false”。为了建立圆形与心形,我们使用 ZStack
来将心形叠在圆形上,如图9.2 所示。SwiftUI 有个 onTapGesture
修饰器可以侦测,点击手势,你可以将它加在任何视图,使其可点击。在 onTapGesture
闭包中,我们切换状态,以改变视图的外观。
在预览画布测试这个 App,则你点击视图时,圆形及心形图示的颜色会改变,但这些变化不会显示动画。
要让这些变化显示动画效果,你只需将 animation
修饰器加到 Circle
与 Image
视图:
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
.animation(.default, value: circleColorChanged)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
.animation(.default, value: heartSizeChanged)
SwiftUI 会自动计算与渲染动画,使视图可以从一个状态流畅转换到另一个状态。再次点击心形,你会见到一个平滑动画。
你不仅可以将 animation
修饰器应用到单一视图中,还可应用于一组视图。举例而言, 你可以将 animation
修饰器加到 ZStack
,将上列的代码重新撰写如下:
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.animation(.default, value: circleColorChanged)
.animation(.default, value: heartSizeChanged)
.onTapGesture {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
其工作原理是完全相同的,SwiftUI 寻找嵌入在 ZStack
中所有的状态变化,并建立动画。
在示例中,我们使用默认动画。SwiftUI 提供许多内建动画供你选择,包括 linear
、easeIn
、easeOut
、easeInOut
与 spring
。“线性动画”(linear animation )是以线性速度来呈现变化,而“缓动动画”(easing animation )则是速度会做变化。详细内容可以参考 www.easings.net 来了解每个 ease 函数的差异。
要使用其他的动画,你只需要在 animation
修饰器中设定特定的动画。例如:你想要使用 spring
动画,则可以将 .default
修改如下:
.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3), value: circleColorChanged)
这会渲染一个基于弹簧特性的动画,使得心形产生心跳的效果。你可以调整阻尼(damping )值与混合(blend )值,来达到不同的效果。
以上是对视图使用隐式动画的方法。我们来看如何使用显式动画来达到相同的结果。如前所述,你需要将状态变化包裹在 withAnimation
区块内。要建立相同的动画效果,你可撰写下列代码:
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.onTapGesture {
withAnimation(.default) {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
}
我们不再使用 animation
修饰器,而是使用 withAnimation
包裹在 onTapGesture
中。withAnimation
调用带入一个动画参数,这里我们指定使用默认动画。
当然,你可修改 withAnimation
,以将动画修改为弹簧动画,如下所示:
withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
使用显式动画,你可以轻松控制想加上动画的状态。举例而言,如果你不想为心形图示的大小变化设定动画时,则可以从 withAnimation
排除该行代码,如下所示:
.onTapGesture {
withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
}
self.heartSizeChanged.toggle()
}
在这个情况下,SwiftUI 将只对圆形与心形的颜色变化设定动画,你不会再看到心形图示的变大动画效果。
你可能想知道我们是否可以使用隐式动画来关闭缩放动画。好的,你仍然可以做到, 可重新调整 .animation
的顺序,以防止 SwiftUI 对特定状态变化设定动画。下列为达到相同效果的程序:
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3), value: circleColorChanged)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3), value: heartColorChanged)
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.onTapGesture {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
对于 Image 视图,我们在 scaleEffect 之前置入 animation(nil)
修饰器,这会将动画取消,scaleEffect
修饰器的状态变化将不会设定动画。
虽然你可以使用隐式动画建立相同的动画,但我认为在这种情况下,使用显式动画会更方便。
SwiftUI 动画的强大之处在于,你不需关心如何对视图设定动画,你只需要提供初始与结束状态,接着 SwiftUI 会找出其余的状态。当你具备了这个概念,即可以建立各种类型的动画。
举例而言,我们来建立一个简单的下载指示器,你通常可以在一些现实世界的应用程序(如 Medium)中找到它。要建立一个如图 9.3 所示的下载指示器,我们从开放式圆环(open ended circle )来开始,如下所示:
Circle()
.trim(from: 0, to: 0.7)
.stroke(Color.green, lineWidth: 5)
.frame(width: 100, height: 100)
那么,我们如何使圆环连续旋转呢?我们可以利用 rotationEffect
与 animation
修饰器。诀窍就是使圆环 360 度连续旋转。以下为代码:
struct ContentView: View {
@State private var isLoading = false
var body: some View {
Circle()
.trim(from: 0, to: 0.7)
.stroke(Color.green, lineWidth: 5)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
.animation(.default.repeatForever(autoreverses: false), value: isLoading)
.onAppear() {
isLoading = true
}
}
}
rotationEffect
修饰器带入旋转角度,在上列的代码中,我们有一个状态变量来控制下载状态。当它设定为true 时,这个旋转角度设定为 360 度以旋转圆环。在 animation
修饰器中,我们指定使用默认动画,不过还有些不同,我们告诉 SwiftUI 要一次又一次重复相同的动画,这就是建立下载动画的诀窍。
*备注: 如果在预览画布中看不到动画,请在模拟器中运行App再试一试。*
如果你想要修改动画的速度,则可以使用线性动画,并指定持续时间,如下所示:
.animation(.linear(duration: 5).repeatForever(autoreverses: false), value: isLoading)
持续时间越久,则动画越慢。
onAppear
修饰器对你而言可能比较陌生,如果你对 UIKit 有所了解的话,这个修饰器和 viewDidAppear
非常相似,当视图出现在画面上时会自动调用。在该代码中,我们将下载状态设定为true,以在视图载入时启动动画。
当你使用此技术,就可以调整设计并开发各种版本的下载指示器。举例而言,你可以将圆弧叠在圆环上,以建立精美的下载指示器,如图 9.4 所示。
代码片段如下所示:
struct ContentView: View {
@State private var isLoading = false
var body: some View {
ZStack {
Circle()
.stroke(Color(.systemGray5), lineWidth: 14)
.frame(width: 100, height: 100)
Circle()
.trim(from: 0, to: 0.2)
.stroke(Color.green, lineWidth: 7)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
.onAppear() {
self.isLoading = true
}
}
}
}
下载指示器不需要为圆形,你也可以使用 Rectangle
或 RoundedRectangle
来建立该指示器。不过,无需修改旋转角度,你可以修改位移值(offset value)来建立如图 9.5 所示的动画。
为了建立动画,我们将两个圆角矩形重叠在一起。上面的矩形比下面的矩形短得多, 当开始载入时,我们将位移值从“-110”修改为“110”。
struct ContentView: View {
@State private var isLoading = false
var body: some View {
ZStack {
Text("Loading")
.font(.system(.body, design: .rounded))
.bold()
.offset(x: 0, y: -25)
RoundedRectangle(cornerRadius: 3)
.stroke(Color(.systemGray5), lineWidth: 3)
.frame(width: 250, height: 3)
RoundedRectangle(cornerRadius: 3)
.stroke(Color.green, lineWidth: 3)
.frame(width: 30, height: 3)
.offset(x: isLoading ? 110 : -110, y: 0)
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
}
.onAppear() {
self.isLoading = true
}
}
}
这会让绿色矩形沿着线条移动。而且,当你一次又一次重复相同的动画,它将变成一个载入动画,图 9.6 说明了位移值。
下载指示器向使用者提供一些回馈,其表示 App 正在处理某些事情。不过,它并没有显示实际进度,如果你需要为使用者提供关于任务进度的更多资讯,则可能需要建立一个如图 9.7 所示的进度指示器。
建立进度指示器的方式与下载指示器非常相似。不过,你需要使用状态变量来追踪进度。以下是建立进度指示器的代码片段:
struct ContentView: View {
@State private var progress: CGFloat = 0.0
var body: some View {
ZStack {
Text("\(Int(progress * 100))%")
.font(.system(.title, design: .rounded))
.bold()
Circle()
.stroke(Color(.systemGray5), lineWidth: 10)
.frame(width: 150, height: 150)
Circle()
.trim(from: 0, to: progress)
.stroke(Color.green, lineWidth: 10)
.frame(width: 150, height: 150)
.rotationEffect(Angle(degrees: -90))
}
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
self.progress += 0.05
print(self.progress)
if self.progress >= 1.0 {
timer.invalidate()
}
}
}
}
}
这里的状态变量不是使用布林值,而是我们使用浮点数来储存状态。为了显示进度, 我们以进度值来设定 trim
修饰器。在现实世界的应用程序中,你可以修改 progress
的值来显示操作的实际进度。为了示范,我们只启用一个计时器,其每半秒修改一次。
SwiftUI 框架不只让你可以控制动画的持续时间,你可以通过 delay
函数来延迟动画, 如下所示:
Animation.default.delay(1.0)
这会将动画延迟1 秒后开始。delay
函数也适用其他动画。
通过混合搭配持续时间值与延迟时间值,你可以实现出一些有趣的动画,如图 9.8 所示的圆点下载指示器。
这个指示器由五个点组成,每个点皆有放大缩小动画,不过各有不同的延迟时间。我们来看代码该如何实现:
struct ContentView: View {
@State private var isLoading = false
var body: some View {
HStack {
ForEach(0...4, id: \.self) { index in
Circle()
.frame(width: 10, height: 10)
.foregroundColor(.green)
.scaleEffect(self.isLoading ? 0 : 1)
.animation(.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index)), value: isLoading)
}
}
.onAppear() {
self.isLoading = true
}
}
}
我们先使用一个 HStack
来水平布局这些圆形,由于这五个圆形(也就是点)皆有相同的大小与颜色,因此我们使用 ForEach
来建立这些圆形。scaleEffect
修饰器是用来缩放圆形的大小,默认是设定为“1”,也就是原来的大小。当开始载入时,该值会修改为“0”, 这将会缩小此点。
用于渲染动画的代码看起来有些复杂,我们来分拆它并逐步研究:
Animation.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index))
第一个部分建立一个持续时间为 0.6 秒的线性动画,该动画会重复运行,因此我们调用 repeatForever
函数。
如果你没有调用 delay
函数来运行这个动画,则所有的点会同时缩放。但是,这不是我们想要的结果,每个点应独立调整大小,而不是全部同时放大/ 缩小,这也是为何我们调用 delay
函数的原因,并对每个点使用不同的延迟时间值。
你可以修改持续时间值与延迟时间值来调整动画。
有时,你可能需要将一个形状(例如:矩形)流畅地变形为另一个形状(例如:圆形)。这该如何实现呢?使用内建的形状与动画,你可以轻松建立如图 9.9 所示的变形。
将矩形变形为圆形的技巧是使用 RoundedRectangle
形状,并为圆角半径的变化设定动画。假设矩形的宽度与高度相同,当圆角半径设定为宽度的一半时,它会变为圆形。以下是变形按钮的实现:
struct ContentView: View {
@State private var recordBegin = false
@State private var recording = false
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: recordBegin ? 30 : 5)
.frame(width: recordBegin ? 60 : 250, height: 60)
.foregroundColor(recordBegin ? .red : .green)
.overlay(
Image(systemName: "mic.fill")
.font(.system(.title))
.foregroundColor(.white)
.scaleEffect(recording ? 0.7 : 1)
)
RoundedRectangle(cornerRadius: recordBegin ? 35 : 10)
.trim(from: 0, to: recordBegin ? 0.0001 : 1)
.stroke(lineWidth: 5)
.frame(width: recordBegin ? 70 : 260, height: 70)
.foregroundColor(.green)
}
.onTapGesture {
withAnimation(Animation.spring()) {
self.recordBegin.toggle()
}
withAnimation(Animation.spring().repeatForever().delay(0.5)) {
self.recording.toggle()
}
}
}
}
这里有两个状态变量:recordBegin
与 recording
,其是控制两个单独的动画。第一个变量控制按钮的变形,如前所述,我们使用圆角半径来实现这个变形。矩形的宽度原先是设定为“250 点”,当使用者点击矩形来触发变形时,这个框架的宽度会变为“60 点”。随着改变,圆角半径变成“30 点”,也就是宽度的一半。
这就是我们将矩形变形为圆形的方法,而且SwiftUI 会自动渲染此变形的动画。
另一方面,recording
状态变量处理了麦克风图片的缩放。当它为录音状态时,我们将缩放比例从“1”修改为“0.7”,藉由重复运行相同的动画,即可建立放大及缩小的动画。
请注意,以上的代码使用显式方法来对视图设定动画。这不是强制性的,依照自己的喜好,你也可以使用隐式动画方法来获得相同的结果。
到目前为止,我们已经讨论了对视图层次(view hierarchy )中已存在的视图设定动画。我们建立动画来放大和缩小视图,或者对视图大小设定动画。
SwiftUI 让开发者不只是做出前述的动画,你可以定义如何从视图层次中插入或移除视图,而在 SwiftUI 中,这就是所谓的“转场”( transition )。框架默认是使用淡入( fade in )与淡出( fade out )转场。不过它内建了几个现成的转场效果,如滑动( slide )、移动( move)、不透明度(opacity )等。当然,你可以开发自己的转场效果,也可以简单的混合搭配各种类型的转场,以建立所需的转场效果。
我们来看一个简单的示例,以更加了解转场的含义以及动画如何运作。建立一个名为 SwiftUITransition
的新项目,并修改 ContentView
如下:
struct ContentView: View {
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.green)
.overlay(
Text("Show details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay(
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
}
}
}
在上列的代码中,我们使用 VStack
垂直布局两个矩形。而我要做的是让这个堆叠可以点击。首先,要隐藏紫色矩形,只有当使用者点击绿色矩形(也就是Show details )时才会显示。
为此,我们需要声明一个状态变量来决定是否显示紫色矩形。将下列这行代码插入 ContentView
中:
@State private var show = false
接下来,我们将紫色矩形包裹在 if
叙述中,如下所示:
if show {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay(
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
}
对于 VStack
,我们加入 onTapGesture
函数来侦测点击,并为状态变化建立动画。请注意,该转场效果与动画要有关联,否则它无法自行运作。
.onTapGesture {
withAnimation(Animation.spring()) {
self.show.toggle()
}
}
当使用者点击堆叠时,我们切换为 show
变量来显示紫色矩形。如果你在模拟器或预览画布中运行这个 App,则应该只会看到绿色矩形,如图 9.12 所示。点击它会显示紫色矩形,而你应可看到一个平滑的淡入与淡出的转场效果。
如前所述,如果你没有指定想使用的转场效果,SwiftUI 将渲染淡入淡出转场。要使用其他的转场效果,则在紫色矩形中加入 transition
修饰器,如下所示:
if show {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay(
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
.transition(.scale(scale: 0, anchor: .bottom))
}
这个 transition
修饰器会带入一个 AnyTransition
类型的参数。这里我们使用scale 转场, 锚点(anchor )设定为 .bottom
,这就是你修改转场效果所需要做的事情。在模拟器中运行该 App,并看一下结果为何,当 App 显示紫色矩形时,你应该会看到一个弹出动画。我建议使用内建的模拟器来测试动画,而不采用 App 预览的方式,因为预览画布可能无法正确的渲染转场动作。
除了 .scale 之外,SwiftUI 框架还有多个内建的转场效果,包括 .opaque
、.offset
、.move
与 .slide
。试着以位移转场(offset transition)取代缩放转场(scale transition),如下所示:
.transition(.offset(x: -600, y: 0))
如此,当紫色矩形插入 VStack
时,它会从左侧滑入。
你可以调用 combined(with:)
方法来将两个或者更多个转场效果结合在一起,以建立更流畅的转场效果。举例而言,如果要结合位移与缩放动画,则可以撰写代码如下:
.transition(AnyTransition.offset(x: -600, y: 0).combined(with: .scale))
如果需要混合三个转场效果的话,则可以参考以下这行的示例代码:
.transition(AnyTransition.offset(x: -600, y: 0).combined(with: .scale).combined(with: .opacity))
在某些情况下,如果你需要定义一个可以重复利用的动画,你可以在 AnyTransition
定义一个扩展(extension ),如下所示:
extension AnyTransition {
static var offsetScaleOpacity: AnyTransition {
AnyTransition.offset(x: -600, y: 0).combined(with: .scale).combined(with: .opacity)
}
}
接着,你可以在 transition
修饰器中使用 offsetScaleOpacity
动画:
.transition(.offsetScaleOpacity)
运行这个App,并再次测试转场效果,看起来很棒吧?
我们刚才讨论的转场皆是对称性的,也就是视图的插入与移除是使用相同的转场效果。举例而言,如果将缩放转场运用于视图,则 SwiftUI 在它插入视图层次时会放大视图,而移除它时,该框架会将其缩回原来大小。
那么,若你想在插入视图时使用缩放转场以及移除视图时使用位移转场呢?这在 SwiftUI 中,即所谓的“不对称转场”( Assymetric Transitions )。要使用这种转场效果非常简单,你只需要调用 .assymetric
方法,来指定插入( insertion )及移除(removal )的转场即可。下列是示例代码:
.transition(.asymmetric(insertion: .scale(scale: 0, anchor: .bottom), removal: .offset(x: -600, y: 0)))
另外,如果你需要重新使用这个转场,则可以在 AnyTransition
定义一个扩展,如下所示:
extension AnyTransition {
static var scaleAndOffset: AnyTransition {
AnyTransition.asymmetric(
insertion: .scale(scale: 0, anchor: .bottom),
removal: .offset(x: -600, y: 00)
)
}
}
修改代码后,请使用内建模拟器来运行这个 App。当紫色矩形出现于屏幕上时,你应该会看到缩放转场,而当你点击矩形时,紫色矩形会滑出屏幕画面。
现在,你应该具备了转场与动画的概念,我们来挑战建立一个精美的按钮,以显示目前操作状态。请输入下列网址:https://www.appcoda.com/wp-content/uploads/2019/10/swiftui-animation-16.gif,来看一下效果。
这个按钮有三种状态:
这是一个非常具挑战性的项目,它将测试你对 SwiftUI 动画与转场的了解。你将需要结合到目前为止所学的知识来制定解决方案。
如图 9.16 所示的示例按钮,这个处理状态大约是 4 秒处理状态约需要 4 秒,你不需要运行实际操作。作为提示,我使用下列的代码来模拟该操作。
private func startProcessing() {
self.loading = true
// 使用DispatchQueue.main.asyncAfter 来模拟操作
// 在现实世界的项目中,你将在这里运行一个任务
// 当任务完成之后,你将完成状态设定为true
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
self.completed = true
}
}
你已经学会了如何实现视图转场,试着结合在第 5 章中建立的卡片视图项目,并建立如下所示的视图转场。当使用者点击卡片时,目前的视图将缩小并淡出,下一个视图将以放大动画显示在前面。
如果你不懂上述的动画, 则可以输入网址: https://www.appcoda.com/wp-content/uploads/2019/10/swiftui-view-animation.gif,来看一下动画效果。
动画在行动 UI 设计中扮演着一个特殊的角色,精心设计的动画可改善使用者体验,并让 UI 的互动具有意义。两个视图之间流畅轻快的转场,将让使用者满意且印象深刻。在 App Store 上有超过 200 万支 App,要使你的 App 脱颖而出,并不容易,不过精心设计的 UI 与动画肯定会产生根本的差别。
话虽如此,但是对于有经验的开发者而言,撰写平滑动画动画也不是一件容易的事。幸运的是,SwiftUI 框架简化了 UI 动画与转场的开发,你只须告诉框架:视图在开始及结束时的状态为何,SwiftUI 会计算出其余的状态,为你渲染出一个流畅且漂亮的动画。
在本章中,我已介绍了基本的原理,不过如你所见,你已经建立了一些令人愉悦的动画与转场效果,最重要的是,只需要几行代码即能办到。
我期望你喜欢本章的内容,并发掘出有用的技巧。在本章所准备的示例档中,有完整的项目与作业解答可以下载: