如何将 2 个视图从一个点动画化到它们出现的最终位置

How to animate 2 views from a single point to their final places on appear

我正在尝试为下面的两个正方形 square 制作动画,从一个公共中心点到它们出现时的最终位置,然后再返回 - 当它们被移除时。

这是我尝试过的方法,但结果是它们不是从重叠位置开始的 - 相反,它们开始时靠近中心的每个位置:

struct ContentView: View {
   @State var matched = true
   @State var show = false
   @Namespace var ns
    
   var body: some View {
      VStack {
         HStack {
            Spacer()
            if show {
               square
                  .matchedGeometryEffect(id: matched ? "match" : "", 
                                         in: ns, anchor: .center, isSource: false)
                  .animation(.easeIn)
                  .transition(.move(edge: .trailing ))
                  .onAppear { withAnimation { matched = false } }                        
                  .onDisappear { withAnimation { matched = true } }
            }
            Spacer()
               .matchedGeometryEffect(id: "match", in: ns, anchor: .center, isSource: true)   
            if show {
               square
                  .matchedGeometryEffect(id: matched ? "match" : ""
                                         in: ns, anchor: .center, isSource: false)
                  .animation(.easeIn)
                  .transition(.move(edge: .leading))
            }
            Spacer()
         }
         Button("show") { withAnimation { show.toggle() } }
      }
   }
}

正方形square简单定义为:

var square: some View {
   Rectangle().foregroundColor(.blue)
      .frame(width: 40, height: 40, alignment: .center)
}

什么样的工作是将 matchedGeometryEffect 附加到 Spacer 中的叠加层,并在所有叠加层中明确指定 properties: .position

不过,只有出现的时候有效,消失的时候无效;还是有差距。

Spacer().overlay(
   Color.clear
     .matchedGeometryEffect(id: "match", in: ns, properties: .position, anchor: .center, isSource: true)
   )

这是实现此效果的正确通用方法吗?如果是,我该如何让它发挥作用?还是我把它复杂化了?

尝试使用与另一个具有相同尺寸的透明正方形相匹配的几何形状并稍微简化一下。有一个内在的不透明度过渡,您可能希望将其移除并替换为某种颜色混合模式。

struct ContentView: View {
  //@State var matched = true
  @State private var show = false
  @Namespace private var ns
  
  var body: some View {
    VStack {
      
      HStack {
        Spacer()
        if show {
          Square(color: .blue)
            .matchedGeometryEffect(id: 1, in: ns, isSource: false)
        }
        Spacer()
          .background(Square(color: .clear)
                        .matchedGeometryEffect(id: show ? 0 : 1, in: ns, isSource: true))
        if show {
          Square(color: .red)
            .matchedGeometryEffect(id: 1, in: ns, isSource: false)
        }
        Spacer()
      }
       
      Button("show") { withAnimation { show.toggle() } }
    }
  }
  
  struct Square: View {
    let color: Color
    var width: CGFloat = 40
    var body: some View {
      Rectangle().foregroundColor(color).frame(width: width, height: width)
    }
  }
  
}