【问题标题】:Animations triggered by events in SwiftUISwiftUI 中事件触发的动画
【发布时间】:2019-07-29 11:17:18
【问题描述】:

SwiftUI 动画通常由状态驱动,这很好,但有时您确实希望触发临时(通常是可逆的)动画以响应某些事件。例如,我想在点击按钮时临时增加按钮的大小(释放按钮时大小的增加和减少都应该作为单个动画发生),但我无法弄清楚这一点.

我认为它可以与转换一起被破解,但不是很好。另外,如果我制作一个使用自动反转的动画,它会增加大小,减小它然后跳回到增加的状态。

【问题讨论】:

  • 你能用这样的东西吗? (它来自 beta 1 或 2,所以它可能不再工作了。)alejandromp.com/blog/2019/06/22/swiftui-reusable-button-style
  • 感谢您提供有趣的链接,但不幸的是,这与 kontiki 的解决方案存在相同的问题:它依赖于状态,在按下状态下按钮有一个大小,在非按下状态下它有正常大小。我需要动画在触发时自动向前播放,然后自动向后播放。

标签: animation swiftui


【解决方案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 中的错误。

    【讨论】:

    • 是的,我明白了,需要进行一些状态更改。但我认为应该可以触发更复杂的动画,比如在状态发生变化时先放大后缩小。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-17
    • 2014-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多