如何在 SwiftUI 中暂停动画?

Posted

技术标签:

【中文标题】如何在 SwiftUI 中暂停动画?【英文标题】:How to pause an animation in SwiftUI? 【发布时间】:2019-09-21 15:31:26 【问题描述】:

withAnimation(_:)方法启动后不知道怎么停止。

我正在使用 SwiftUI 编写我的第一个应用程序,我想创建一个进度圈,该进度圈将由用户使用按钮控制 - 开始按钮,它将开始动画,其中圆圈将在结尾处未填充,停止按钮将有保存填充点的实际状态并停止动画。

我的主要观点:

struct MainView: View 
  @State private var fillPoint = 1.0 
  @State private var animationDuration = 10.0

  private var ring: Ring 
    let ring = Ring(fillPoint: self.fillPoint)
    return ring
  

  var body: some View 
    VStack 
      ring.stroke(Color.red, lineWidth: 15.0)
        .frame(width: 200, height: 200)
        .padding(40)
      HStack 
        Button(action: 
          withAnimation(.easeIn(duration: self.animationDuration)) 
              self.fillPoint = 0
          
        ) 
          Text("Start")
        
        Button(action: 
          // what should I do here?
        ) 
          Text("Stop")
        
      
    
  

还有 Ring 的结构:

struct Ring: Shape 
  var startArcAngle: Double = 360.0

  var fillPoint: Double 
    willSet 
      startArcAngle = 360 * newValue
    
  

  internal var animatableData: Double 
    get  return fillPoint 
    set  fillPoint = newValue 
  

  internal func path(in rect: CGRect) -> Path 
    let endArcAngle = 0.0

    var path = Path()

    path.addArc(center: CGPoint(x: rect.size.width / 2,
                                y: rect.size.height / 2),
                radius: rect.size.width / 2,
                startAngle: .degrees(startArcAngle - 90),
                endAngle: .degrees(endArcAngle - 90),
                clockwise: true)

    return path
  

我试图操纵animatableData 值,但是在 Ring 的结构之外它总是返回 0.0(如果它启动,我的代码将在动画结束时实现该值)并且在 Ring 的结构内部它会按照我的意愿打印(0.96xxxx -> 0.94xxxx 等),但是在 Ring 的结构之外使用它总是返回 1.0 或 0.0。

【问题讨论】:

【参考方案1】:

好像没有停止动画的控件。

由于您的要求是在进行中开始和停止抽签,因此另一种解决方案是使用Timer。棘手的一点是根据计时器持续时间清除电弧。

这是我在您的MainView 中更改的代码:

注意:根据您的选择调整动画持续时间。

struct MainView: View 
    @State private var fillPoint = 1.0
    @State private var animationDuration = 10.0
    @State private var  stopAnimation = true
    @State private var countdownTimer: Timer?
    
    private var ring: Ring 
        let ring = Ring(fillPoint: self.fillPoint)
        return ring
    
    
    var body: some View 
        VStack 
            ring.stroke(Color.red, lineWidth: 15.0)
                .frame(width: 200, height: 200)
                .padding(40)
                .animation(self.stopAnimation ? nil : .easeIn(duration: 0.1))
            HStack 
                Button(action: 
                    self.stopAnimation = false
                    self.countdownTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block:  _ in
                        guard self.animationDuration > 0 else 
                            self.countdownTimer?.invalidate()
                            return
                        
                        self.fillPoint = self.animationDuration/10
                        self.animationDuration -= 0.1
                    )
                ) 
                    Text("Start")
                
                Button(action: 
                    self.countdownTimer?.invalidate()
                    self.stopAnimation = true
                ) 
                    Text("Stop")
                
            
        
    

【讨论】:

效果几乎很好!但是如果它必须是动态的(用户将多次更改计时器的持续时间,但仅在进度环结束或重置之后)countdownTimer 中的self.fillPoint 必须除以总时间值(这里它将是 10 , 但在其他情况下 10 将是无效值)。无论如何,感谢您的帮助,这确实是我想要的。【参考方案2】:

你不能打断一个动画,但你可以让它看起来像一个打断。 您可以使用组,当视图没有动画时,您可以使用静态视图。 这是一个详细说明的例子;

struct RingView: View 
@ObservedObject var viewModel: ViewModel

@State private var animatedBonusRemaining: Double = 1

private func startBonusTimeAnimation() 
    animatedBonusRemaining = viewModel.remainingTimeRatio
    withAnimation(.linear(duration: viewModel.remainingDuration)) 
        animatedBonusRemaining = 0
    


var body: some View 
    ZStack 
        Pie()
            .opacity(0.4)
        Group 
            if viewModel.isRunning 
                Pie(startAngle: Angle(degrees: -90),
                    endAngle: Angle(degrees: (-360 * animatedBonusRemaining) - 90))
             else 
                Pie(startAngle: Angle(degrees: -90),
                    endAngle: Angle(degrees: (-360 * viewModel.remainingTimeRatio) - 90))
            
        
    
    .aspectRatio(1.0, contentMode: .fill)
    .onTapGesture 
        routine.toggleIsRunning()
        if routine.isRunning 
            startBonusTimeAnimation()
        
    

剩余比率是一个计算属性,很容易通过剩余时间占总持续时间来计算。 我在我的视图模型中使用了一个计时器,但不要将它暴露给 View,因为我认为视图本身没有计时器的工作。它可以通过 withAnimation 闭包进行动画处理。我只使用计时器来检测完成情况。

【讨论】:

以上是关于如何在 SwiftUI 中暂停动画?的主要内容,如果未能解决你的问题,请参考以下文章

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

如何在 SwiftUI 中禁用位置动画?

如何阻止视图在 SwiftUI 中出现动画?

如何在 SwiftUI 中动画/过渡文本值更改

如何在 SwiftUI 中点击手势时放大缩小按钮动画?

如何在 SwiftUI 中使用 Lottie Button 动画启用操作?