SwiftUI 中事件触发的动画
Posted
技术标签:
【中文标题】SwiftUI 中事件触发的动画【英文标题】:Animations triggered by events in SwiftUI 【发布时间】:2019-07-29 11:17:18 【问题描述】:SwiftUI 动画通常由状态驱动,这很好,但有时您确实希望触发临时(通常是可逆的)动画以响应某些事件。例如,我想在点击按钮时临时增加按钮的大小(释放按钮时大小的增加和减少都应该作为单个动画发生),但我无法弄清楚这一点.
我认为它可以与转换一起被破解,但不是很好。另外,如果我制作一个使用自动反转的动画,它会增加大小,减小它然后跳回到增加的状态。
【问题讨论】:
你能用这样的东西吗? (它来自 beta 1 或 2,所以它可能不再工作了。)alejandromp.com/blog/2019/06/22/swiftui-reusable-button-style 感谢您提供有趣的链接,但不幸的是,这与 kontiki 的解决方案存在相同的问题:它依赖于状态,在按下状态下按钮有一个大小,在非按下状态下它有正常大小。我需要动画在触发时自动向前播放,然后自动向后播放。 【参考方案1】:您可以使用绑定到 longPressAction() 的 @State
变量:
为 Beta 5 更新代码:
struct ContentView: View
var body: some View
HStack
Spacer()
MyButton(label: "Button 1")
Spacer()
MyButton(label: "Button 2")
Spacer()
MyButton(label: "Button 3")
Spacer()
struct MyButton: View
let label: String
@State private var pressed = false
var body: some View
return Text(label)
.font(.title)
.foregroundColor(.white)
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).foregroundColor(.green))
.scaleEffect(self.pressed ? 1.2 : 1.0)
.onLongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity, pressing: pressing in
withAnimation(.easeInOut(duration: 0.2))
self.pressed = pressing
, perform: )
【讨论】:
非常感谢朋友的回答,这确实如其所说,但不幸的是,在我的情况下,我只有一个事件来触发放大和缩小,所以我没有'认为我不能使用纯状态方法。在这个例子中,当你松开手指时,我需要动画的两个部分都运行!【参考方案2】:这也是我一直从事的事情。
到目前为止,我的解决方案依赖于应用 GeometryEffect 修饰符并滥用其方法 effectValue 在某些动画期间连续调用的事实。所以想要的效果实际上是插值从 0..1 开始的变换,在 0.5 有主要效果,在 0 或 1 没有效果
效果很好,它适用于所有视图,而不仅仅是按钮,无需依赖触摸事件或按钮样式,但在我看来仍然是一种 hack。
随机旋转和缩放效果示例:
代码示例:
struct ButtonEffect: GeometryEffect
var offset: Double // 0...1
var animatableData: Double
get offset
set offset = newValue
func effectValue(size: CGSize) -> ProjectionTransform
let effectValue = abs(sin(offset*Double.pi))
let scaleFactor = 1+0.2*effectValue
let affineTransform = CGAffineTransform(rotationAngle: CGFloat(effectValue)).translatedBy(x: -size.width/2, y: -size.height/2).scaledBy(x: CGFloat(scaleFactor), y: CGFloat(scaleFactor))
return ProjectionTransform(affineTransform)
struct ButtonActionView: View
@State var animOffset: Double = 0
var body: some View
Button(action:
withAnimation(.spring())
self.animOffset += 1
)
Text("Press ME")
.padding()
.background(Color.yellow)
.modifier(ButtonEffect(offset: animOffset))
【讨论】:
非常感谢,我会测试一下。我以前从未听说过 GeometryEffect! 不客气,我刚刚发表了一篇关于 GeometryEffect 的简短 blogpost,如果你有兴趣的话 @PavelZak 你将如何解决居中视图的问题?假设您只想缩放视图。顺便说一句,干得好!【参考方案3】:我相信这就是您所追求的。 (这就是我解决这个问题的方法)
基于 dfd 的链接,我想出了这个,它不依赖于任何 @State 变量。您只需实现自己的按钮样式。 不需要 Timers、@Binding、@State 或其他复杂的解决方法。
import SwiftUI
struct MyCustomPressButton: ButtonStyle
func makeBody(configuration: Self.Configuration) -> some View
configuration.label
.padding(10)
.cornerRadius(10)
.scaleEffect(configuration.isPressed ? 0.8 : 1.0)
struct Play: View
var body: some View
Button("Tap")
.buttonStyle(MyCustomPressButton())
.animation(.easeIn(duration: 0.2))
struct Play_Previews: PreviewProvider
static var previews: some View
Play()
【讨论】:
【参考方案4】:在 SwiftUI 中无法绕过通过状态更新的需要。你需要有一些属性只在短时间内为真,然后切换回来。
以下动画从小到大再回来。
struct ViewPlayground: View
@State var enlargeIt = false
var body: some View
Button("Event!")
withAnimation
self.enlargeIt = true
.background(Momentary(doIt: self.$enlargeIt))
.scaleEffect(self.enlargeIt ? 2.0 : 1.0)
struct Momentary: View
@Binding var doIt: Bool
var delay: TimeInterval = 0.35
var body: some View
Group
if self.doIt
ZStack Spacer()
.onAppear
DispatchQueue.main.asyncAfter(deadline: .now() + self.delay)
withAnimation
self.doIt = false
不幸的是,delay
是设置 self.enlargeIt = true
时产生动画所必需的。没有它,它只会动画回退。不确定这是否是 Beta 4 中的错误。
【讨论】:
是的,我明白了,需要进行一些状态更改。但我认为应该可以触发更复杂的动画,比如在状态发生变化时先放大后缩小。以上是关于SwiftUI 中事件触发的动画的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI解决List子项无法正确触发onAppear和onDisappear事件的问题
SwiftUI解决List子项无法正确触发onAppear和onDisappear事件的问题