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事件的问题

SwiftUI 接收自定义事件

SwiftUI:自定义模态动画

SwiftUI:onChange(of:scenePhase)没有触发

当 Swiftui Picker 滚动其列表时触发的事件