如何在 SwiftUI 中从圆形转换为矩形?

问题描述 投票:0回答:2

在我尝试构建的应用程序中,我有一个带圆圈的滚动视图。当用户点击圆形时,它应该平滑地过渡到全屏矩形。

为了简单起见,我们将只专注于尝试从小圆圈过渡到全屏矩形。

第 1 步:检查是否可动画

使用以下代码,我们可以在圆形和更大的矩形之间设置动画。证明

RoundedRectangle()
视图和
frame
视图修改器都是可动画的。

struct ContentView: View {
    @State private var cornerRadius = 60.0
    @State private var size = 100.0

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: cornerRadius)
                .foregroundStyle(.orange)
                .frame(width: size, height: size)
        }
        .preferredColorScheme(.dark)
        .animation(.linear, value: cornerRadius)
        .animation(.linear, value: size)
        .onTapGesture {
            cornerRadius = cornerRadius == 0.0 ? 60.0 : 0.0
            size = size == 100.0 ? 300.0 : 100.0
        }
    }
}

这就是我想要的视图之间转换的效果。

第 2 步:视图之间的转换

这就是事情变得复杂的地方。我们有两种不同的视图:紧凑型和全屏版本。为了清楚起见,我将紧凑版设为橙色,将全屏版设为红色,我这样称呼它们。

我们从前面的示例中删除了可动画属性,并将其替换为在 true 和 false 之间切换的 isFullScreen 属性。使用

matchedGeometryEffect
我们可以匹配尺寸和框架。据我所知,SwiftUI 现在应该拥有在两个视图之间进行插值并平滑过渡的所有构建块。然而事实并非如此。

struct ContentView: View {
    @Namespace private var namespace
    
    @State private var isFullScreen = false

    var body: some View {
        ZStack {
            if !isFullScreen {
                RoundedRectangle(cornerRadius: 60.0)
                    .foregroundStyle(.orange)
                    .matchedGeometryEffect(id: "item", in: namespace)
                    .frame(width: 100.0, height: 100.0)
            } else {
                RoundedRectangle(cornerRadius: 0.0)
                    .foregroundStyle(.red)
                    .matchedGeometryEffect(id: "item", in: namespace)
                    .frame(width: 300.0, height: 300.0)
            }
        }
        .preferredColorScheme(.dark)
        .animation(.linear, value: isFullScreen)
        .onTapGesture {
            isFullScreen.toggle()
        }
    }
}

从淡入淡出动画和重影看来,SwiftUI 不知道该怎么办。因此,当转换橙色视图时,将获得它提供的角半径,并且不会将此属性从原始 60.0 动画到新的 0.0。至于红色视图,它不会将该属性从 60.0 动画到 0.0。它唯一做的就是为尺寸设置动画。

swift animation swiftui transition
2个回答
1
投票

您可以尝试这种方法,如示例代码所示。似乎对我有用。

 struct ContentView: View {
     @State private var goFullScreen = false
     
     var body: some View {
         RoundedRectangle(cornerRadius: goFullScreen ? 0 : 60.0)
             .fill(.orange)
             .frame(maxWidth: goFullScreen ? .infinity : 300,
                    maxHeight: goFullScreen ? .infinity : 300)
             .clipShape(RoundedRectangle(cornerRadius: goFullScreen ? 0 : 60.0))
             .animation(.linear, value: goFullScreen)
             .onTapGesture {
                 goFullScreen.toggle()
             }
             .preferredColorScheme(.dark)
     }
 }

1
投票

根据您的评论,如果您希望在两个单独的视图之间转换时发生动画,那么最好的选择是使用角半径的状态变量。它并不完美,但比你以前的更好:

struct ContentView: View {
    @Namespace private var namespace
    @State private var goFullScreen = false
    private static let roundedCornerRadius = CGFloat(60.0)
    @State private var cornerRadius = ContentView.roundedCornerRadius

    var body: some View {
        ZStack {
            if goFullScreen == false {
                RoundedRectangle(cornerRadius: cornerRadius)
                    .fill(.orange)
                    .matchedGeometryEffect(id: "box", in: namespace, isSource: !goFullScreen)
                    .frame(width: 300, height: 300)
                    .onAppear { cornerRadius = ContentView.roundedCornerRadius }
            } else {
                RoundedRectangle(cornerRadius: cornerRadius)
                    .fill(.orange)
                    .matchedGeometryEffect(id: "box", in: namespace, isSource: goFullScreen)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .onAppear { cornerRadius = 0 }
            }
        }
        .animation(.linear, value: goFullScreen)
        .animation(.linear, value: cornerRadius)
        .onTapGesture {
            goFullScreen.toggle()
        }
        .preferredColorScheme(.dark)
    }
}

编辑跟进您的进一步评论:

  1. 尽管我们将动画设置为线性,但插入的视图不知何故会快速变大。两种视图都应该模仿行为,因此不会出现重影。

我怀疑这是因为

matchedGeometryEffect
正在缩放视图,因此从绝对意义上讲,半径大小不匹配。您可以通过放弃使用 matchesGeometryEffect 并使用大小的状态变量来解决此问题(就像角半径的状态变量一样)。使用此状态变量来控制 maxWidth 和 maxHeight。

  1. 如果反转动画,插入的视图将不会为角设置动画。

如果您的意思是切换 if-else,那么请确保用正确的值初始化cornerRadius。您还需要更改匹配的几何效果的标志

isSource

  1. 如果您中断动画并且它再次变大,角将保持圆角。

是的,如果在动画发生时双击,则错误的半径可能会生效。这也可以通过添加 onDisappear 回调来解决。

实现该效果的另一种方法是完全避免使用过渡。您可以使用不透明度来控制可见性,然后使用彼此完全模仿的动画。这使得圆角半径和大小的变化能够平滑地进行动画处理。但不确定它是否适用于现实世界。

struct ContentView: View {
    @Namespace private var namespace
    @State private var goFullScreen = false

    var body: some View {
        ZStack {
            Color.orange
                .frame(maxWidth: goFullScreen ? .infinity : 300, maxHeight: goFullScreen ? .infinity : 300)
                .cornerRadius(goFullScreen ? 0 : 60)
                .opacity(goFullScreen ? 0 : 1)
            Color.orange
                .frame(maxWidth: goFullScreen ? .infinity : 300, maxHeight: goFullScreen ? .infinity : 300)
                .cornerRadius(goFullScreen ? 0 : 60)
                .opacity(goFullScreen ? 1 : 0)
        }
        .animation(.linear, value: goFullScreen)
        .onTapGesture {
            goFullScreen.toggle()
        }
        .preferredColorScheme(.dark)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.