在 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使用

SwiftUI之深入解析高级动画的路径Paths

Compose 动画 : 高可定制性的动画 Animatable

我可以使用转换作为转换属性的值吗?

AI论文探讨室·A+·第102期-Vid2Actor: Free-viewpoint Animatable Person Synthesis from Video in the Wild

如何在 SwiftUI 中使用 Realm