SwiftUI 中的 animatableData 是做什么的?

ray*_*eja 6 animation core-animation ios swift swiftui

我正在研究animatableData我制作的自定义形状的属性,但我无法真正想象它的作用以及系统在哪里使用它。

我不明白系统如何知道animatableData当状态发生变化时应该插入哪些属性。我也不明白这get部分属性animatableData是被系统用来做什么的。我唯一理解的是,当变量发生更改时,SwiftUI 会将animatableData属性更新为原始值和最终值之间的所有中间值。@State

animatableData如果有人可以给出系统使用的非常详细的事件顺序,我将非常感激。尽可能详细,因为我是那种即使不理解 1% 的东西也会感到潦草的人之一(但是如果我确实有任何问题,我会在评论中问你)。

提前致谢!

PS 我尝试在 getter 中返回一个常量,animatableData但我的动画仍然完美运行,这让我更加困惑。如果可以的话请告诉我吸气剂的用途。

Moz*_*ler 1

您问题的最简单答案是使用用于绘制视图的值覆盖默认值animatableData[由协议继承]。Animatable以下是如何执行此操作的示例:

    var animatableData: Double {
      get { return percent }
      set { percent = newValue }
   }
Run Code Online (Sandbox Code Playgroud)

这是给您的一个例子。它:

  • 在父视图上绘制一个圆环。

当百分比值(定义时连接的百分比 animatableData)发生变化时,动画会使用更新时的百分比值沿着定义的圆的圆周绘制一条线来更新视图。

动画圈

import SwiftUI

/// This repeats an animation until 5 seconds elapse
struct SimpleAnswer: View {
   /// the start/stop sentinel
   static var shouldAnimate = true
   /// the percentage of the circumference (arc) to draw
   @State var percent = 0.0

   /// animation duration/delay values
   var animationDuration: Double { return 1.0 }
   var animationDelay: Double { return  0.2 }
   var exitAnimationDuration: Double { return 0.3 }
   var finalAnimationDuration: Double { return 1.0 }
   var minAnimationInterval: Double { return 0.1 }

   var body: some View {
      ZStack {
         AnimatingOverlay(percent: percent)
            .stroke(Color.yellow, lineWidth: 8.0)
            .rotationEffect(.degrees(-90))
            .aspectRatio(1, contentMode: .fit)
            .padding(20)
            .onAppear() {
               self.performAnimations()
            }
            .frame(width: 150, height: 150,
                   alignment: .center)
         Spacer()
      }
      .background(Color.blue)
      .edgesIgnoringSafeArea(.all)
   }

   func performAnimations() {
      run()
      if SimpleAnswer.shouldAnimate {
         restartAnimation()
      }
      /// Stop the Animation after 5 seconds
      DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: { SimpleAnswer.shouldAnimate = false })
   }

   func run() {
      withAnimation(.easeIn(duration: animationDuration)) {
         percent = 1
      }
      let deadline: DispatchTime = .now() + animationDuration + animationDelay
      DispatchQueue.main.asyncAfter(deadline: deadline) {
         withAnimation(.easeOut(duration: self.exitAnimationDuration)) {
         }
         withAnimation(.easeOut(duration: self.minAnimationInterval)) {
         }
      }
   }

   func restartAnimation() {
      let deadline: DispatchTime = .now() + 2 * animationDuration + finalAnimationDuration
      DispatchQueue.main.asyncAfter(deadline: deadline) {
         self.percent = 0
         self.performAnimations()
      }
   }
}

/// Draws a Ring on the parent View
/// By default, `Shape` returns the instance of `EmptyAnimatableData` struct as its animatableData.
/// All you have to do is replace this default `EmptyAnimatableData` with a different value.
/// As the value of percent changes, the animation updates the view
struct AnimatingOverlay: Shape {
   var percent: Double

   func path(in rect: CGRect) -> Path {
      let end = percent * 360
      var p = Path()

      p.addArc(center: CGPoint(x: rect.size.width/2, y: rect.size.width/2),
               radius: rect.size.width/2,
               startAngle: Angle(degrees: 0),
               endAngle: Angle(degrees: end),
               clockwise: false)
      return p
   }

   /// This example defines `percent` as the value to animate by
   /// overriding the value of `animatableData`
   /// inherited as Animatable.animatableData
   var animatableData: Double {
      get { return percent }
      set { percent = newValue }
   }
}

#if DEBUG
struct SimpleAnswer_Previews : PreviewProvider {
   static var previews: some View {
      SimpleAnswer()
   }
}
#endif
Run Code Online (Sandbox Code Playgroud)

我发现这些链接可以帮助我回答您的问题。您应该会发现它们也很有用。

Wenderlich - 如何使用 SwiftUI 创建启动屏幕

Majid - 可动画化值的魔力

SwiftUI 中的动画 - Majid