SwiftUI matchedGeometryEffect动画可以与过渡组合吗?

bpsygsoo  于 2023-08-02  发布在  Swift
关注(0)|答案(1)|浏览(112)

我想将默认过渡(AnyTransition.opacity)替换为另一个应用了matchedGeometryEffect()修改器的视图过渡。我尝试了几种不同的技术,但似乎没有一种能做到这一点。
首先,这是matchedGeometryEffect本身。ItemView使用默认动画从一个容器“移动”到另一个容器。

struct ContentView: View {
    @Namespace private var itemMovement
    @State private var isInFirst = true
    
    var body: some View {
        VStack {
            Text("First Container")
            HStack {
                if isInFirst {
                    ItemView()
                        .matchedGeometryEffect(id: "item", in: itemMovement)
                }
            }
            .padding()
            .border(.red)
            
            Text("Second Container")
            HStack {
                if !isInFirst {
                    ItemView()
                        .matchedGeometryEffect(id: "item", in: itemMovement)
                }
            }
            .padding()
            .border(.red)
            
            Button("Move Item") {
                withAnimation {
                    isInFirst.toggle()
                }
            }
        }
    }
}

struct ItemView: View {
    var body: some View {
        Circle()
            .foregroundStyle(.blue)
            .frame(width: 100, height: 100)
    }
}

字符串
x1c 0d1x的数据
仔细观察圆,可以看到动画中间的颜色变浅。我相信这是默认转换AnyTransition.opacity的效果。为了删除该转换,我尝试在Circle视图中显式添加AnyTransition.identity

struct ContentView: View {
    @Namespace private var itemMovement
    @State private var isInFirst = true
    
    var body: some View {
        VStack {
            Text("First Container")
            HStack {
                if isInFirst {
                    ItemView()
                        .transition(.identity) // <-- here
                        .matchedGeometryEffect(id: "item", in: itemMovement)
                }
            }
            .padding()
            .border(.red)
            
            Text("Second Container")
            HStack {
                if !isInFirst {
                    ItemView()
                        .transition(.identity) // <-- here
                        .matchedGeometryEffect(id: "item", in: itemMovement)
                }
            }
            .padding()
            .border(.red)
            
            Button("Move Item") {
                withAnimation {
                    isInFirst.toggle()
                }
            }
        }
    }
}


然而,结果似乎是圆视图不再使用matchedGeometryEffect动画。我已经尝试了一些变化,例如将.transition()修改器放置在.matchedGeometryEffect()修改器之后,并且一次仅在一个视图上使用.transition()修改器-希望了解为什么会发生这种跳转到最终位置并制定新的解决方案。我还没想好
谁能帮我弄清楚如何将过渡与匹配的几何效果结合起来?除了这个简化的例子,我希望创建一个自定义的过渡,基于一个可动画化的视图修改器,它在内部将一个.rotation3DEffect()应用到视图。


relj7zay

relj7zay1#

这个例子似乎显示了一个圆圈从一个容器“移动”到另一个容器。实际上,它显示了两个圆,其中matchedGeometryEffect用于协调位置并平滑过渡。你观察到一个小的颜色变化的原因是因为第一个圆圈在第二个圆圈淡入的同时淡出。如果你仔细观察,HStacks的红色边框和第二个容器的标签也可以在圆形形状下看到,因为两个圆形在中间点都是不透明的。
matchedGeometryEffectdocumentation声明,如果“isSource = true的组中当前插入的视图的数量不正好是一个”,则结果未定义。所以如果你真的想使用matchedGeometryEffect,我建议你在第一种情况下添加isSource: isInFirst,在第二种情况下添加isSource: !isInFirst。然而,即使您通过使用.scale转换(如前面的评论中所解释的)解决了淡入淡出问题,在实现您在问题中描述的.rotation3DEffect时,您可能仍然会遇到困难。这是因为如果在两个单独的视图之间转换,效果将不会设置动画。
因此,除了使用matchedGeometryEffect,另一种实现动画的方法是将位置的变化应用于单个圆。大概是这样的:

var body: some View {
        VStack {
            Text("First Container")
            VStack {
                HStack {
                    if isInFirst {
                        ItemView().hidden()
                    }
                }
                .padding()
                .border(.red)

                Text("Second Container")
                HStack {
                    if !isInFirst {
                        ItemView().hidden()
                    }
                }
                .padding()
                .border(.red)
            }
            .overlay(alignment: isInFirst ? .top : .bottom) {
                ItemView().padding()
            }
            Button("Move Item") {
                withAnimation {
                    isInFirst.toggle()
                }
            }
        }
    }

字符串
注意事项:

  • 现在只有一个可见的圆,这显示在叠加图中
  • 隐藏的占位符用于在两个备选位置中为可见圆保留空间
  • 因为它实际上是同一个圆圈在改变位置,所以没有渐变效果,并且圆圈始终保持不透明
  • matchedGeometryEffect根本不需要
  • 如果你在移动的视图上应用一个rotation3DEffect(正如你所描述的),这也会被平滑地动画化。
  • 编辑 * 根据您的评论,您最好的选择可能是两种方法的组合:
  • 将动画更改应用于单个视图(可以是叠加,也可以是ZStack中的层)
  • 使用matchedGeometryEffect控制其位置
  • 要匹配的源可以是容器本身
  • 像以前一样使用隐藏的占位符来保留空间。

这样,您也应该能够设置旋转效果的动画。这里它是工作:

struct ContentView: View {
    @Namespace private var itemMovement
    @State private var isInFirst = true

    var body: some View {
        ZStack {
            VStack {
                Text("First Container")
                HStack {
                    if isInFirst {
                        ItemView().hidden()
                    }
                }
                .matchedGeometryEffect(id: "frame1", in: itemMovement, isSource: isInFirst)
                .padding()
                .border(.red)

                Text("Second Container")
                HStack {
                    if !isInFirst {
                        ItemView().hidden()
                    }
                }
                .matchedGeometryEffect(id: "frame2", in: itemMovement, isSource: !isInFirst)
                .padding()
                .border(.red)

                Button("Move Item") {
                    withAnimation {
                        isInFirst.toggle()
                    }
                }
            }
            ItemView()
                .matchedGeometryEffect(
                    id: isInFirst ? "frame1" : "frame2",
                    in: itemMovement,
                    isSource: false
                )
        }
    }
}

相关问题