SwiftUI 五彩纸屑动画

Posted

技术标签:

【中文标题】SwiftUI 五彩纸屑动画【英文标题】:SwiftUI Confetti Animation 【发布时间】:2020-11-22 16:09:07 【问题描述】:

注意:我解决了这个问题。如果你对动画感兴趣,我用 SPM 创建了一个包,可以在https://github.com/simibac/ConfettiSwiftUI上找到

所以我正在尝试在 SwiftUI 中创建五彩纸屑动画。这是我目前所拥有的:

这一切都按预期工作,并使用以下代码完成:

struct Movement
    var x: CGFloat
    var y: CGFloat
    var z: CGFloat
    var opacity: Double


struct FancyButtonViewModel: View 
    @State var animate = [false]
    @State var finishedAnimationCouter = 0
    @State var counter = 0
    
    var body: some View 
        VStack
            ZStack
                ForEach(finishedAnimationCouter...counter, id:\.self) i in
                    ConfettiContainer(animate:$animate[i], finishedAnimationCouter:$finishedAnimationCouter, num:1)
                
            
            
            Button("Confetti")
                animate[counter].toggle()
                animate.append(false)
                counter+=1
            
        
    


struct ConfettiContainer: View 
    @Binding var animate:Bool
    @Binding var finishedAnimationCouter:Int

    var num:Int
    
    var body: some View
        ZStack
            ForEach(0...num-1, id:\.self) _ in
                Confetti(animate: $animate, finishedAnimationCouter:$finishedAnimationCouter)
            
        
        .onChange(of: animate)_ in
            DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) 
                finishedAnimationCouter+=1
            
        
    


struct Confetti: View
    @Binding var animate:Bool
    @Binding var finishedAnimationCouter:Int
    @State var movement = Movement(x: 0, y: 0, z: 1, opacity: 0)
    

    var body: some View
        Text("❤️")
            .frame(width: 50, height: 50, alignment: .center)
            .offset(x: movement.x, y: movement.y)
            .scaleEffect(movement.z)
            .opacity(movement.opacity)
            .onChange(of: animate)  _ in
                withAnimation(Animation.easeOut(duration: 0.4)) 
                    movement.opacity = 1
                    movement.x = CGFloat.random(in: -150...150)
                    movement.y = -300 * CGFloat.random(in: 0.7...1)
                

                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) 
                    withAnimation(Animation.easeIn(duration: 3)) 
                        movement.y = 200
                        movement.opacity = 0.0
                    
                
        
    

现在我想用动画五彩纸屑视图替换 Heart Emoji。因此,我创建了以下视图:

struct ConfettiView: View 
    @State var animate = false
    @State var xSpeed = Double.random(in: 0.7...2)
    @State var zSpeed = Double.random(in: 1...2)
    @State var anchor = CGFloat.random(in: 0...1).rounded()
    
    var body: some View 
        Rectangle()
            .frame(width: 20, height: 20, alignment: .center)
            .onAppear(perform:  animate = true )
            .rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 1, y: 0, z: 0))
            .animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false))
            .rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
            .animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false))
    

这也可以按预期工作。但是,如果我将 Text("❤️") 替换为。 ConfettiView() 我得到了一些意想不到的动画,如下所示。 (我将五彩纸屑的数量减少到 1,以便更好地观察动画)。动画中断了什么?

【问题讨论】:

好消息,你完全错了!令人惊讶的是,ios 完全内置了粒子动画系统。完整教程:***.com/a/43075797/294884 很高兴知道谢谢,但这是一个只能使用纯 SwiftUI 的实验 当然!我真的鼓励你在 SwiftUI 中使用粒子系统进行测试,也许是作为测试。您会对性能和灵活性感到惊讶。祝你好运 !!!看起来不错 【参考方案1】:

您的动画重叠,要解决您需要加入带有状态值的内部动画。

这里是固定变体。使用 Xcode 12.1 / iOS 14.1 测试

struct ConfettiView: View 
    @State var animate = false
    @State var xSpeed = Double.random(in: 0.7...2)
    @State var zSpeed = Double.random(in: 1...2)
    @State var anchor = CGFloat.random(in: 0...1).rounded()
    
    var body: some View 
        Rectangle()
            .frame(width: 20, height: 20, alignment: .center)
            .onAppear(perform:  animate = true )
            .rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 1, y: 0, z: 0))
            .animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false), value: animate)
            .rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
            .animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false), value: animate)
    

【讨论】:

如果我将Rectangle() 替换为shapes.randomElement()(其中形状定义为let shapes:[AnyView] = [AnyView(Rectangle()), AnyView(Circle())]),您是否也知道为什么动画会因某些五彩纸屑而中断?

以上是关于SwiftUI 五彩纸屑动画的主要内容,如果未能解决你的问题,请参考以下文章

如何使用JavaScript创建五彩纸屑效果

HTML5 画布下落的五彩纸屑/雪多个对象

如何在 SwiftUI 的 ScrollView 中制作动画? SwiftUI 中的手风琴风格动画

SwiftUI:动画更改依赖于@ObjectBinding

在动画偏移时动画删除/添加 SwiftUI 视图

SwiftUI 中的动画状态变化