SwiftUI五彩纸屑动画 [英] SwiftUI Confetti Animation

查看:140
本文介绍了SwiftUI五彩纸屑动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:我已经解决了这个问题.如果您对动画感兴趣,可以使用SPM创建一个程序包,该程序包可以在

这一切都可以按预期完成,并通过以下代码完成:

 结构运动{var x:CGFloatvar y:CGFloatvar z:CGFloatvar opacity:Double}struct FancyButtonViewModel:视图{@State var animate = [false]@State var finishedAnimationCouter = 0@State var计数器= 0var body:some View {VStack {ZStack {ForEach(finishedAnimationCouter ... counter,id:\.self){我在ConfettiContainer(animate:$ animate [i],finishAnimationCouter:$ finishedAnimationCouter,num:1)}}按钮(五彩纸屑"){animate [counter] .toggle()animate.append(false)计数器+ = 1}}}}struct ConfettiContainer:查看{@Binding var animate:布尔@Binding var finishAnimationCouter:Intvar num:Intvar主体:一些视图{ZStack {ForEach(0 ... num-1,id:\.self){_ in五彩纸屑(animate:$ animate,finishAnimationCouter:$ finishedAnimationCouter)}}.onChange(of:animate){_ inDispatchQueue.main.asyncAfter(最后期限:.now()+ 3.5){finishAnimationCouter + = 1}}}}五彩纸屑结构:查看{@Binding var animate:布尔@Binding var finishAnimationCouter:Int@State var运动=运动(x:0,y:0,z:1,不透明度:0)var主体:一些视图{文字(❤️").frame(宽度:50,高度:50,对齐方式:.center).offset(x:运动.x,y:运动.y).scaleEffect(movement.z).opacity(movement.opacity).onChange(of:animate){_ inwithAnimation(Animation.easeOut(duration:0.4)){movement.opacity = 1movement.x = CGFloat.random(in:-150 ... 150)运动.y = -300 * CGFloat.random(in:0.7 ... 1)}DispatchQueue.main.asyncAfter(最后期限:.now()+ 0.4){withAnimation(Animation.easeIn(duration:3)){运动.y = 200运动.不透明度= 0.0}}}}} 

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

  struct ConfettiView:查看{@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 {长方形().frame(宽度:20,高度:20,对齐方式:.center).onAppear(执行:{animate = true)).rotation3DEffect(.degrees(animate?360:0),轴:(x:1,y:0,z:0)).animation(Animation.linear(duration:xSpeed).repeatForever(autoreverses:false)).rotation3DEffect(.degrees(animate?360:0),轴:(x:0,y:0,z:1),锚点:UnitPoint(x:锚点,y:锚点)).animation(Animation.linear(duration:zSpeed).repeatForever(autoreverses:false))}} 

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

解决方案

您的动画重叠,以解决您需要将内部状态值与状态值连接起来的情况.

此处是固定变体.经过Xcode 12.1/iOS 14.1的测试

  struct ConfettiView:查看{@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 {长方形().frame(宽度:20,高度:20,对齐方式:.center).onAppear(执行:{animate = true)).rotation3DEffect(.degrees(animate?360:0),轴:(x:1,y:0,z:0)).animation(Animation.linear(duration:xSpeed).repeatForever(autoreverses:false),value:animate).rotation3DEffect(.degrees(animate?360:0),轴:(x:0,y:0,z:1),锚点:UnitPoint(x:锚点,y:锚点)).animation(Animation.linear(duration:zSpeed).repeatForever(autoreverses:false),value:animate)}} 

Note: I solved the issue. If you are interested in the animation, I created a package with SPM and is available on https://github.com/simibac/ConfettiSwiftUI

So I am trying to create a confetti animation in SwiftUI. This is what I have so far:

This works all as expected and is done with the following code:

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
                    }
                }
        }
    }
}

Now I want to replace the Heart Emoji with an animated confetti view. Thus, I created the following view:

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))
    }
}

This works as expected as well. However, if I replace the Text("❤️") with. ConfettiView() I get some unexpected animation as shown below. (I reduced the number of confettis to 1 so the animation can be observed better). What does the animation break?

解决方案

Your animations overlaps, to solve you need to join inner one with state value.

Here is fixed variant. Tested with 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)
    }
}

这篇关于SwiftUI五彩纸屑动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆