【译】[SwiftUI 100 天] Animations - part3
2020/3/31 23:02:33
本文主要是介绍【译】[SwiftUI 100 天] Animations - part3,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
译自 Controlling the animation stack
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
控制动画栈
在这一节,我想要把两个你已经理解的东西放在一起,单独理解都没问题,但放在一起可能有点伤脑筋。
之前我们已经理解 modifiers 的顺序会如何影响视图。因此,我们写过这样的代码:
Button("Tap Me") { // do nothing } .background(Color.blue) .frame(width: 200, height: 200) .foregroundColor(.white)复制代码
它跟下面这种代码的效果是不一样的:
Button("Tap Me") { // do nothing } .frame(width: 200, height: 200) .background(Color.blue) .foregroundColor(.white)复制代码
这是因为如果我们在调整 frame 之前给背景上色的话,只有原始的区域被上色,而不是之后 frame 扩展之后的区域。回忆一下,这种表象下面是 SwiftUI 用 modifier 包裹视图的机制,它使得我们可以多次应用相同的 modifier —— 我们可以通过重复多次 background() 和 padding() 来创建一个有一层层条纹的边框效果。
这里是第一个概念:modifier 的顺序很重要,因为 SwiftUI 以 modifiers 应用的顺序来包裹视图。
第二个概念是我们可以应用一个 animation() modifier 到一个视图,以便这个视图能获得一个隐式的动画。
为了演示上面两个概念,我们可以修改按钮的代码,让它根据不同的状态展示不同的颜色。首先,我们需要定义状态:
@State private var enabled = false复制代码
在按钮的 action 中切换启动状态:
self.enabled.toggle()复制代码
然后在 background() modifier 里用一个条件化的值,让按钮即可以是蓝色的,也可以是红色的:
.background(enabled ? Color.blue : Color.red)复制代码
最后,我们添加 animation() modifier 给按钮,让上面那些变化都以动画的方式呈现:
.animation(.default)复制代码
运行 app ,点击按钮,你会看到按钮在蓝色和红色之间变化。
目前为止,modifier 顺序很重要,并且我们可以给一个视图多次添加同一个 modifier ,我们还可以用 animation() modifier 创建隐式动画。这些都很清晰对吧?
是的,接下来是伤脑筋的地方。
你可以多次使用 animation() modifier ,并且你使用它们的顺序也很重要。
下面是演示时刻。我们把这个 modifier 加到按钮所有的 modifiers 之后:
.clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0))复制代码
这会让按钮根据 enabled 状态在方形和圆角矩形之前变换。
当你运行程序,点击按钮,你会看到按钮会以动画的方式在红色和蓝色之间变化,但方形和圆角矩形之间的变换确是直接跳变的 —— 这部分没有执行动画。
希望你能想到我们接下来要做什么:我需要你把 clipShape() modifier 移到 animation 之前,像这样:
.frame(width: 200, height: 200) .background(enabled ? Color.blue : Color.red) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0)) .animation(.default)复制代码
当你再次运行代码,你会发现背景色和按钮形状都会执行动画了。
因此,我们应用动画的顺序也很重要:只有发生 animation() modifier 之前的变化才会以动画的方式呈现。
接下来是有趣的部分:如果我们应用多个 animation() modifiers ,每一个动画会控制在它前面直到上一个动画的所有视图。这种特性使得我们可以用各种方式动画演示属性的变化,而不局限于一种方式。
举个例子,我们可以让颜色变化以默认动画的方式呈现,让形状变化以弹簧动画的方式呈现:
Button("Tap Me") { self.enabled.toggle() } .frame(width: 200, height: 200) .background(enabled ? Color.blue : Color.red) .animation(.default) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0)) .animation(.interpolatingSpring(stiffness: 10, damping: 1))复制代码
你可以根据需要使用任意多的 animation() modifiers 来构建你的设计,这让我们可以把一个状态变化按需拆解成许多部分。
你还可以通过传入 nil 来禁用动画。举个例子,你可能希望颜色变化立即发生,而形状变化保持动画,那么可以把代码改成这样:
Button("Tap Me") { self.enabled.toggle() } .frame(width: 200, height: 200) .background(enabled ? Color.blue : Color.red) .animation(nil) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0)) .animation(.interpolatingSpring(stiffness: 10, damping: 1))复制代码
上面这种程度的控制,如果没有多个 animation() modifiers 是无法实现的。
译自 Animating gestures
动画手势
SwiftUI 允许我们给任意视图添加手势支持,并且这些手势的效果也能以动画呈现。我们稍后再来看手势的细节,现在让我们先尝试一个很简单的东西:一个可以在屏幕上拖来拖去的卡片,放手的时候自动跳过原来的位置。
首先是初始布局:
struct ContentView: View { var body: some View { LinearGradient(gradient: Gradient(colors: [.yellow, .red]), startPoint: .topLeading, endPoint: .bottomTrailing) .frame(width: 300, height: 200) .clipShape(RoundedRectangle(cornerRadius: 10)) } }复制代码
上面的代码在屏幕中央绘制了一个卡片样子的视图。我们希望能基于手指的位置来在屏幕上移动它,一共需要三步。
第一步,我们需要一个存储拖拽数量的状态:
@State private var dragAmount = CGSize.zero复制代码
第二步,我们用这个数值来影响卡片在屏幕上的位置。SwiftUI 有一个专门的 modifier 实现这种需求,它叫 offset() ,可以让我们调整视图的 X 和 Y 坐标,不影响这个视图周围的其他视图。你可以传入具体的 X 坐标和 Y 坐标,或者 offset() 也可以直接接收一个 CGSize 。
把下面的 modifier 加到卡片的 gradient 。
.offset(dragAmount)复制代码
接下来是重要的部分。我们要创建一个 DragGesture 并且附着到卡片。Drag gestures 有两个 modifiers 我们会用到。一个是 onChanged() ,它在用户手指在屏幕上移动时执行一个闭包,第二个是 onEnded() ,在用户手指从屏幕上抬起时执行一个闭包,结束拖拽过程。
两个闭包都提供一个参数,描述拖拽操作 —— 从哪里开始,现在位于哪里,移动了多远,等等。对于 onChanged() modifier ,我们将读取拖放的平移量,这个值告诉我们从起点移动了多远 —— 我们把它直接赋给 dragAmount 以便视图跟着手势一起移动。对于 onEnded() ,我们将完全忽略输入,因为这里我们要把 dragAmount 设回 0 。
把这个 modifier 添加到 linear gradient :
.gesture( DragGesture() .onChanged { self.dragAmount = $0.translation } .onEnded { _ in self.dragAmount = .zero } )复制代码
运行代码,拖拽卡片,当你放手的时候,卡片会直接跳回中央。卡片的偏移量是由 dragAmount 决定的,最终由拖拽手势决定。
现在,逻辑都已经工作,我们需要让它们以动画的方式鲜活起来,这里有两个选项:添加一个隐式动画,演示拖拽和释放,或者只添加一个显式的动画,只演示释放。
对于隐式动画的选项,添加这个 modifier 到 linear gradient :
.animation(.spring())复制代码
当你拖拽卡片时,卡片会稍稍延迟一下,然后移动到拖拽的位置,这是由于弹簧动画的关系。如果你突然移动的话,弹簧动画还会呈现出超过位置的效果。
对于显式动画的选项:我们去掉 animation() modifier ,然后把拖拽手势的 onEnded() 代码改成这样:
.onEnded { _ in withAnimation(.spring()) { self.dragAmount = .zero } }复制代码
这样一来卡片会立即跟随你的拖拽 (因为已经不做动画了) ,而释放过程会有动画。
如果我们以一些延迟来结合 offset 动画和拖拽动作,可以用很少的代码创建出非常有趣的动画。
下面演示这种动画,我们先以一组文本视图的形式呈现 “Hello SwiftUI” 的每个字母,每个字母都有背景色和由状态控制的偏移量。由于字符串本质上可以理解为字符数组的高级版本,所以我们可以通过字符串得到一个字符数组,就像这样:Array("Hello SwiftUI")
先奉上代码:
struct ContentView: View { let letters = Array("Hello SwiftUI") @State private var enabled = false @State private var dragAmount = CGSize.zero var body: some View { HStack(spacing: 0) { ForEach(0..<letters.count) { num in Text(String(self.letters[num])) .padding(5) .font(.title) .background(self.enabled ? Color.blue : Color.red) .offset(self.dragAmount) .animation(Animation.default.delay(Double(num) / 20)) } } .gesture( DragGesture() .onChanged { self.dragAmount = $0.translation } .onEnded { _ in self.dragAmount = .zero self.enabled.toggle() } ) } }复制代码
运行代码,你可以拖拽任意一个字母,然后你会发现整个字符串的其他字母都会跟随这个字母移动,并且伴随不同数量的延迟,整个效果就像蛇行一样。SwiftUI 还会在你手指释放时用动画呈现字母蓝色和红色之间的变化,伴随字母移动到中央的过程。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~
这篇关于【译】[SwiftUI 100 天] Animations - part3的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值