SwiftUI 切换 State 时如何实现连续动画?

问题描述 投票:0回答:1

我有以下用户界面:

enter image description here

Red Rectangle
旋转速度基于滑块的值。我使用
.linear(duration:).repeatForever(autoreverses: false)
(基于滑块值的持续时间)和
.id
修改器来实现此目的,以便在滑块值更改时停止当前动画。

struct ContentView: View {
    @State private var level: Double = 0
    @State private var rotation: Double = 0
    
    var body: some View {
        VStack(spacing: 16) {
            Rectangle().fill(.red)
                .frame(width: 100, height: 100)
                .id(level)
                .rotationEffect(Angle(degrees: rotation))
                .animation(level > 0 ? .linear(duration: 2 / level).repeatForever(autoreverses: false) : .linear(duration: 2), value: rotation)
            
            Spacer().frame(height: 12)
            Slider(value: $level, in: 0...3, step: 1)
            HStack {
                Text("0")
                    .fontWeight(.semibold)
                Spacer()
                Text("1")
                    .fontWeight(.semibold)
                Spacer()
                Text("2")
                    .fontWeight(.semibold)
                Spacer()
                Text("3")
                    .fontWeight(.semibold)
            }
        }
        .padding(16)
        .onChange(of: level, perform: { _ in
            rotation += 360
        })
    }
}

我当前的问题是,当用户更改滑块的值时,由于

Rectangle
.id
重置为初始状态,因此会产生故障。

这是我的代码当前如何工作的视频链接:https://imgur.com/a/6bsUJzm

我的问题是当用户更改滑块的值时如何在状态之间实现平滑过渡?任何见解或建议将不胜感激!

ios swift swiftui
1个回答
0
投票

解决方法之一是避免在动画中使用

.repeatForever
。相反:

  • 旋转四分之一圈。
  • 完成后,检查级别是否 > 0。
  • 如果是,则再触发四分之一圈。

自 iOS 17 起,使用

withAnimation
触发的动画可以有
completion
回调。所以这将是触发后续动画的一种方式。然而,这样做会产生相当断断续续的动画。

另一种技术是使用

Animatable
ViewModifier
来跟踪动画并在动画完成时执行操作。这样做工作起来会更顺利。 SwiftUI动画完成动画周期的答案提供了一个可用于此目的的通用修饰符(这是我的答案)。那篇文章也涉及连续轮换,所以这是一个非常相似的问题。

这是更新后的示例。更多注意事项:

  • 应删除

    .id
    修饰符。

  • 动画修改器出现在AnimationCompletionCallback之后

    非常重要。

  • 由于不再使用

    .repeatForever

    ,因此可以简化动画修饰符(不需要三元运算符)。

  • 将滑块标签的

    .fontWeight

     应用到 
    HStack
     更简单,而不是应用到每个单独的标签。

  • 您之前使用的是

    .onChange

     的已弃用版本。如果实际上您的目标是早于 iOS 17 的 iOS 版本,则只需删除参数内的 
    oldVal
     即可。

var body: some View { VStack(spacing: 16) { Rectangle() .fill(.red) .frame(width: 100, height: 100) .rotationEffect(Angle(degrees: rotation)) // See https://stackoverflow.com/a/76969841/20386264 .modifier( AnimationCompletionCallback(animatedValue: rotation) { if level > 0 { rotation += 90 } } ) .animation(.linear(duration: 2 / max(1, level)), value: rotation) Spacer().frame(height: 12) Slider(value: $level, in: 0...3, step: 1) HStack { Text("0") Spacer() Text("1") Spacer() Text("2") Spacer() Text("3") } .fontWeight(.semibold) } .padding(16) .onChange(of: level) { oldVal, newVal in if oldVal == 0 { rotation += 90 } } }

Animation

© www.soinside.com 2019 - 2024. All rights reserved.