4

考虑这个简单的例子:

struct TestView: View {
    @State private var enabled = false
    
    var body: some View {
        Circle()
            .foregroundColor(.red)
            .overlay(
                Circle()
                    .foregroundColor(.blue)
                    .frame(width: 50, height: 50)
                    .animation(.spring())
                
            )
            .frame(width: 100, height: 100)
            .offset(x: 0, y: enabled ? -50 : 50)
            .animation(.easeIn(duration: 1.0))
            .onTapGesture{
                enabled.toggle()
            }
            
    }
}

点击生成的圆圈时会产生以下动画:

在此处输入图像描述

嵌套圆使用其自己的计时功能(弹簧)动画到其新的全局位置,而外圆/父视图使​​用其 easeInOut 计时功能动画到新的全局位置。理想情况下,如果修饰符在父视图抽象级别上工作(在这种情况下,offset,还有类似的东西position),所有子级都将使用父级计时函数进行动画处理。

这可能是因为 SwiftUI 渲染引擎在布局过程中为视图层次结构中所有受影响的子项计算新的全局属性,并根据附加的最具体的动画修饰符对每个属性的更改进行动画处理(即使在这种情况下,相对位置父母中的孩子不会改变)。当视图的子视图可能运行自己的复杂动画(父视图不知道也不应该知道)时,这使得做像正确翻译视图这样简单的事情变得非常困难。

我注意到的另一个怪癖是,在这个特定的示例中,在偏移修改器之前animation(nil)添加一个修改器会破坏外圆上的动画,尽管其余部分直接附加到偏移修改器。这违反了我对这些修改器如何链接的理解,其中(根据此来源)动画修改器适用于它需要的所有视图,直到下一个嵌套动画修改器。.easeInOut

4

2 回答 2

4

这对我来说似乎是一个错误,可能值得向 Apple 报告。通过在 .offset 修饰符之前添加另一个修饰符,我确实找到了一个奇怪的解决方法:

struct TestView: View {
    @State private var enabled = false
    
    var body: some View {
        Circle()
            .foregroundColor(.red)
            .overlay(
                Circle()
                    .foregroundColor(.blue)
                    .frame(width: 50, height: 50)
                    .animation(.spring())
                
            )
            .frame(width: 100, height: 100)
            .scaleEffect(1) // <------- this doesn't do anything but somehow overrides the animation
            .offset(x: 0, y: enabled ? -50 : 50)
            .animation(.easeIn(duration: 1.0))
            .onTapGesture{
                enabled.toggle()
            }
            
    }
}

我已经用 scaleEffect、rotationEffect 和 rotation3DEffect 进行了尝试,它们都可以工作。并非所有修饰符都有效。我真的很好奇为什么这些特定的会这样做。

.animation(nil) 对我来说似乎也是一个错误。

于 2021-01-05T02:55:10.140 回答
3

我将尝试澄清您在问题中提到的一些要点:

理想情况下,如果修饰符在父视图抽象级别工作,所有子节点都将使用父节点计时功能进行动画处理

考虑以下示例:

Circle()
    .overlay(
        Circle()
            .frame(width: 50, height: 50)
            .foregroundColor(.blue)
    )
    .frame(width: 100, height: 100)
    .foregroundColor(.red)

如果你说的是真的,那么嵌套Circle应该是红色的。在 SwiftUI 中,父修饰符不会覆盖子修饰符。


在偏移量修改器中断外圆上的动画之前添加一个动画(无)修改器

这与上面的示例完全相同。

通过调用:

Circle()
    .foregroundColor(.red)
    .overlay(
        Circle()
            .foregroundColor(.blue)
            .frame(width: 50, height: 50)
            .animation(.spring())
    )
    .frame(width: 100, height: 100)
    .animation(nil) // with `nil` animation
    .offset(x: 0, y: enabled ? -50 : 50)
    .animation(.easeIn(duration: 1.0))
    .onTapGesture {
        enabled.toggle()
    }

您仅将 to 设置animation为父nil级。子视图有自己的animation修饰符。

如果内部Circle没有animation修饰符,它会起作用,因此父修饰符也将适用于这个特定的子视图:

Circle()
    .foregroundColor(.red)
    .overlay(
        Circle()
            .foregroundColor(.blue)
            .frame(width: 50, height: 50)
            // .animation(.spring())
    )
    .frame(width: 100, height: 100)
    .animation(nil) // now this will work for both parent and child view
    .offset(x: 0, y: enabled ? -50 : 50)
    .animation(.easeIn(duration: 1.0))
    .onTapGesture {
        enabled.toggle()
    }

这违反了我对这些修改器如何链接的理解,其中 [...] 动画修改器适用于它需要的所有视图,直到下一个嵌套动画修改器。

直到下一个嵌套动画修饰符是这里的关键。设置一直animation(nil)有效,直到找到另一个animation修饰符。


如何覆盖嵌套的偏移/位置动画?

最简单的选择就是删除嵌套.animation(.spring())修饰符。

如果这是不可能的,您可以从两个圈子创建一个视图,例如使用他们的答案scaleEffect中建议的@bze12 。

从以下文档scaleEffect

相对于锚点,按给定的垂直和水平大小量缩放此视图的渲染输出。

这意味着下面的代码:

Circle()
    .foregroundColor(.red)
    .overlay(
        Circle()
            .foregroundColor(.blue)
            .frame(width: 50, height: 50)
            .animation(.spring())
    )
    .frame(width: 100, height: 100)
    .scaleEffect()
    .offset(x: 0, y: enabled ? -50 : 50)
    .animation(.easeIn(duration: 1.0))
    .onTapGesture {
        enabled.toggle()
    }

也可以读作:

scaledView
    .offset(x: 0, y: enabled ? -50 : 50)
    .animation(.easeIn(duration: 1.0))
    .onTapGesture {
        enabled.toggle()
    }

如您所见,现在只有一个animation修改器,它适用于生成的缩放视图。

于 2021-01-05T17:47:44.310 回答