8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

SwiftUI:停止永远重复的动画

BJonas88 1月前

36 0

我希望屏幕上有某种“徽章”,当条件满足时,它会反复从正常大小跳变到更大,然后回到正常大小,直到不再满足条件。我...

我希望屏幕上有一个“徽章”,当条件满足时,它会反复从正常大小弹跳到更大再回到正常大小,直到条件不再满足。但我似乎无法让徽章停止“弹跳”。 一旦开始,就无法停止。

我尝试过的方法: 我尝试过使用一些动画,但它们可以分为使用“repeatForever”来实现所需效果的动画和不使用“repeatForever”的动画。例如:

Animation.default.repeatForever(autoreverses: true)

Animation.spring(response: 1, dampingFraction: 0, blendDuration: 1) (将阻尼设置为 0 可使其永远持续下去)

然后用 .animation(nil) 替换它。似乎不起作用。有人有什么想法吗?提前非常感谢!以下是重现它的代码:

struct theProblem: View {
    @State var active: Bool = false

    var body: some View {
        Circle()
            .scaleEffect( active ? 1.08: 1)
            .animation( active ? Animation.default.repeatForever(autoreverses: true): nil )
            .frame(width: 100, height: 100)
            .onTapGesture {
                self.active = !self.active

        }
    }
}
帖子版权声明 1、本帖标题:SwiftUI:停止永远重复的动画
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由BJonas88在本站《ios》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我明白了!

    如果用 替换动画,则 .repeatForever() 使用 的动画 nil . 如果用相同的动画但不带 替换它,则使用 的动画将停止 .repeatForever() 。(或者,使用任何其他停止的动画,因此,您可以使用持续时间为 0 的线性动画来立即停止)

    换句话说,这是行不通的: .animation(active ? Animation.default.repeatForever() : nil)

    但这确实有效: .animation(active ? Animation.default.repeatForever() : Animation.default)

    为了使其更具可读性和易于使用,我将其放入扩展中,您可以像这样使用: .animation(Animation.default.repeat(while: active))

    这是一个使用我的扩展程序的交互式示例,您可以使用实时预览来测试它:

    import SwiftUI
    
    extension Animation {
        func `repeat`(while expression: Bool, autoreverses: Bool = true) -> Animation {
            if expression {
                return self.repeatForever(autoreverses: autoreverses)
            } else {
                return self
            }
        }
    }
    
    struct TheSolution: View {
        @State var active: Bool = false
        var body: some View {
            Circle()
                .scaleEffect( active ? 1.08: 1)
                .animation(Animation.default.repeat(while: active))
                .frame(width: 100, height: 100)
                .onTapGesture {
                    self.active.toggle()
                }
        }
    }
    
    struct TheSolution_Previews: PreviewProvider {
        static var previews: some View {
            TheSolution()
        }
    }
    

    据我所知,一旦您指定了动画,它就不会消失,直到您的视图完全停止。因此,如果您有一个设置为永远重复并自动反转的 .default 动画,然后您指定一个持续时间为 4 的线性动画,您会注意到默认的重复动画仍在继续,但它的移动速度越来越慢,直到它在 4 秒结束时完全停止。所以我们通过线性动画将默认动画停止。

  • 谢谢你 - 我也遇到了同样的问题。你有没有为此提交雷达?似乎将动画设置为 nil 就足够了,但事实并非如此

  • 救命!!我只需要替换没有 repeatForever 的动画 :) 谢谢你的提示

  • 经历了很多事情后,我找到了一种对我有用的方法。至少目前是这样,直到我有时间找到更好的方法。

    struct WiggleAnimation<Content: View>: View {
        var content: Content
        @Binding var animate: Bool
        @State private var wave = true
    
        var body: some View {
            ZStack {
                content
                if animate {
                    Image(systemName: "minus.circle.fill")
                        .foregroundColor(Color(.systemGray))
                        .offset(x: -25, y: -25)
                }
            }
            .id(animate) //THIS IS THE MAGIC
            .onChange(of: animate) { newValue in
                if newValue {
                    let baseAnimation = Animation.linear(duration: 0.15)
                    withAnimation(baseAnimation.repeatForever(autoreverses: true)) {
                        wave.toggle()
                    }
                }
            }
            .rotationEffect(.degrees(animate ? (wave ? 2.5 : -2.5) : 0.0),
                            anchor: .center)
        }
    
        init(animate: Binding<Bool>,
             @ViewBuilder content: @escaping () -> Content) {
            self.content = content()
            self._animate = animate
        }
    }
    

    使用

    @State private var editMode = false
    
    WiggleAnimation(animate: $editMode) {
        VStack {
            Image(systemName: image)
                .resizable()
                .frame(width: UIScreen.screenWidth * 0.1,
                       height: UIScreen.screenWidth * 0.1)
                .padding()
                .foregroundColor(.white)
                .background(.gray)
            
            Text(text)
                .multilineTextAlignment(.center)
                .font(KMFont.tiny)
                .foregroundColor(.black)
        }
    }
    

    它是如何工作的?这里的 .id(animate)修饰符不会刷新视图,而只是用新的视图替换它,因此它回到原来的状态。

    这可能不是最好的解决方案,但就我的情况而言它有效。

  • 非常感谢!我已经为这个问题苦苦挣扎了几个小时。id() 正是我所缺少的,它可以强制重置状态,从而重置我的无限动画,以便它以更新的初始值和目标值重新启动。

  • 如何使用交易

    在下面的代码中,我根据状态关闭或打开动画 active

    警告: 请务必使用 withAnimation ,否则将不起作用

    @State var active: Bool = false
    
    var body: some View {
        Circle()
            .scaleEffect(active ? 1.08: 1)
            .animation(Animation.default.repeatForever(autoreverses: true), value: active)
            .frame(width: 100, height: 100)
            .onTapGesture {
                useTransaction()
            }
    }
    
    func useTransaction() {
        var transaction = Transaction()
        transaction.disablesAnimations = active ? true : false
        
        withTransaction(transaction) {
            withAnimation {
                active.toggle()
            }
        }
    }
    
  • 您的代码没有任何问题,所以我认为这是 Apple 的缺陷。似乎有很多隐式动画(至少在 Xcode 11.2 中)。无论如何...

    我建议考虑下面提供的可实现预期行为的替代方法。

    struct TestAnimationDeactivate: View {
        @State var active: Bool = false
    
        var body: some View {
            VStack {
                if active {
                    BlinkBadge()
                } else {
                    Badge()
                }
            }
            .frame(width: 100, height: 100)
            .onTapGesture {
                self.active.toggle()
            }
        }
    }
    
    struct Badge: View {
        var body: some View {
            Circle()
        }
    }
    
    struct BlinkBadge: View {
        @State private var animating = false
        var body: some View {
            Circle()
                .scaleEffect(animating ? 1.08: 1)
                .animation(Animation.default.repeatForever(autoreverses: true))
                .onAppear {
                    self.animating = true
                }
        }
    }
    
    struct TestAnimationDeactivate_Previews: PreviewProvider {
        static var previews: some View {
            TestAnimationDeactivate()
        }
    }
    
  • 哇,谢谢!为了确保我跟上您的步伐,SwiftUI 有一些我们看不到或无法轻易看到的秘密忍者黑匣子。作为一种解决方法,直到更容易解决这个问题,我们可以在两个看起来相同的形状之间交替,但其中一个在弹跳,另一个没有,从而给人一种弹跳停止的错觉。我确实注意到,即使视图不可见,动画似乎仍在继续!哈,它真的是不可阻挡的!非常感谢您的回复!

  • 我很确定这是正确的方法。虽然我有点希望它不是。WWDC21 关于视图标识的演讲提供了线索,说明为什么你应该采用这种方法来删除自动重复动画。

  • 引用 11

    中的示例时也遇到了类似的问题 Hacking with Swift

    .animation(active ? Animation.default.repeatForever() : Animation.default)

    在 Xcode 13.2.1 上对我来说也不起作用。我找到的解决方案是将动画封装在自定义 ViewModifier 中。下面的代码说明了这一点;大按钮在活动和非活动动画之间切换。

    `

    struct ContentView: View {
        @State private var animationAmount = 1.0
        @State private var animationEnabled = false
        
        var body: some View {
            VStack {
                
                Button("Tap Me") {
                    // We would like to stop the animation
                    animationEnabled.toggle()
                    animationAmount = animationEnabled ? 2 : 1
                }
                .onAppear {
                    animationAmount = 2
                    animationEnabled = true
                }
                .padding(50)
                .background(.red)
                .foregroundColor(.white)
                .clipShape(Circle())
                .overlay(
                    Circle()
                        .stroke(.red)
                        .scaleEffect(animationAmount)
                        .opacity(2 - animationAmount)
                )
                .modifier(AnimatedCircle(animationAmount: $animationAmount, animationEnabled: $animationEnabled))
            }
        }
    }
    
    struct AnimatedCircle: ViewModifier {
        @Binding var animationAmount: Double
        @Binding var animationEnabled: Bool
        func body(content: Content) -> some View {
            if animationEnabled {
                return content.animation(.easeInOut(duration: 2).repeatForever(autoreverses: false),value: animationAmount)
            }
            else {
                return content.animation(.easeInOut(duration: 0),value: animationAmount)
            }
        }
    }
    

    `

    这可能不是最好的解决方案,但它确实有效。我希望它能帮助到一些人。

返回
作者最近主题: