在 SwiftUI 中使用 animatableData 制作动画的问题
Posted
技术标签:
【中文标题】在 SwiftUI 中使用 animatableData 制作动画的问题【英文标题】:Problem animating with animatableData in SwiftUI 【发布时间】:2020-03-29 02:06:36 【问题描述】:SwiftUI 不仅为我们提供了自动动画……它还允许我们使用 animatableData
属性控制动画的发生方式。太酷了!
只有我不能让它工作。以下代码在屏幕上显示一个钟面(数字 0…11 ????)并显示一个拨动开关。切换开关将数字旋转 180 度,动画。或者这就是我的意图。但是不是每个数字都围绕数字圈走一条动画路径,而是每个数字沿着一条直线移动到它的新位置……所以数字圈塌陷到圆圈的中心点,然后重新爆炸成它的新配置:
animatableData
属性未被 SwiftUI 引用。知道为什么吗?
(请注意,使用标准的旋转动画是行不通的,因为这会使所有的数字都颠倒过来。)
import SwiftUI
struct ContentView: View
@State var state: Bool = false
var body: some View
VStack
CircleView(hoursOffset: CGFloat(state ? 6 : 0 ))
.aspectRatio(contentMode: .fit)
Toggle(isOn: $state, label: Text("Rotate 180°?") )
.frame(maxWidth: 200).padding()
extension CGPoint
static func onCircle(hours: CGFloat, size: CGFloat) -> CGPoint
let radians = (3 - hours) * CGFloat.pi / CGFloat (6)
let hypotenuse = size / 2
return CGPoint(x: size / 2 + 0.8 * hypotenuse * cos(radians),
y: size / 2 - 0.8 * hypotenuse * sin(radians))
struct CircleView: View
var hoursOffset: CGFloat
public var animatableData: CGFloat
get hoursOffset
set self.hoursOffset = newValue
var body: some View
GeometryReader() geo in
ForEach(0..<12) hour in
ZStack
Text("\(hour)")
.position(CGPoint.onCircle(hours: CGFloat(hour) + self.hoursOffset, size: geo.size.width))
.animation(Animation.easeInOut(duration: 2.0))
【问题讨论】:
【参考方案1】:这是可能的方法。使用 Xcode 11.4 / ios 13.4 测试。仅在 View 中 animatableData
不起作用 - 您必须将其放入 AnimatableModifier
。
struct CircleMoving: AnimatableModifier
var hoursOffset: CGFloat
var hour: CGFloat
var size: CGFloat
public var animatableData: CGFloat
get hoursOffset
set self.hoursOffset = newValue
func body(content: Content) -> some View
content
.position(CGPoint.onCircle(hours: hour + hoursOffset, size: size))
struct CircleView: View
var hoursOffset: CGFloat
var body: some View
GeometryReader() geo in
ForEach(0..<12) hour in
ZStack
Text("\(hour)")
.modifier(CircleMoving(hoursOffset: self.hoursOffset, hour: CGFloat(hour), size: geo.size.width))
.animation(.easeInOut(duration: 3.0), value: hoursOffset) // !!!
可选:我还建议您将点值四舍五入以获得更好的完整性,但这并不重要。
extension CGPoint
static func onCircle(hours: CGFloat, size: CGFloat) -> CGPoint
let radians = (3 - hours) * CGFloat.pi / CGFloat (6)
let hypotenuse = size / 2
return CGPoint(x: (size / 2 + 0.8 * hypotenuse * cos(radians)).rounded(.up),
y: (size / 2 - 0.8 * hypotenuse * sin(radians)).rounded(.up))
【讨论】:
谢谢你,@Asperi - 我很感激......再次!我去玩它。如果您愿意,请告诉我:您是如何了解 AnimatableModifier 的以及为什么它不起作用,例如,像我最初那样直接使用onCircle()
,而不是在修饰符中使用?我找不到任何文档,我很想了解,所以如果您有任何链接或提示要分享,我将非常感激!
有什么想法吗,@Asperi?见上面的评论。谢谢!
@Anton, here 你可以找到好的开始材料,下面只是实验。 :)
谢谢你,@Asperi。实际上,这就是我学到很多关于 AnimatableData 的地方——以及为什么在我看来,根据他的时钟示例,我不需要使用 AnimatableModifier(他不用于时钟)。它也是我找到的唯一一个 AnimatableModifier 参考资料。不幸的是,我没有发现他对 AnimatibleModifier 的解释对于理解为什么在这里需要它很有帮助。虽然他的例子很棒!
这是累积动画的效果。通过在CircleView
中添加value: hoursOffset
进行修复。查看更新。【参考方案2】:
问题是邪恶的数学......你正在计算端点。 Apple 只是将步骤从一个位置添加到另一个位置(作为一条线),所以这就是您所看到的。如果你从 0 切换到 1,它“看起来”像一个圆形移动,但它也是线性的;) 解决方案是,根据您的小时偏移值手动计算圆上的 x,y 位置
【讨论】:
谢谢你,@Chris。但恐怕我没明白你的意思。我定义的CGPoint.onCircle
函数确实计算了圆上的所有中间点,而不仅仅是端点,我定义的animatableData
属性指定所有这些中间值都应该在动画中使用。对吗?
或者,@Chris,您可以分享您提供的屏幕截图的代码吗?以上是关于在 SwiftUI 中使用 animatableData 制作动画的问题的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI之深入解析高级动画的AnimatableModifier使用
Compose 动画 : 高可定制性的动画 Animatable
AI论文探讨室·A+·第102期-Vid2Actor: Free-viewpoint Animatable Person Synthesis from Video in the Wild