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

mzillmmw  于 11个月前  发布在  Swift
关注(0)|答案(4)|浏览(143)

在我尝试构建的应用程序中,我有一个带有圆圈的滚动视图。当用户点击一个圆圈时,它应该平滑地过渡到全屏矩形。
为了简单起见,我们将只专注于尝试从一个小圆圈过渡到一个全屏矩形。

第一步:检查Animatable

通过下面的代码,我们可以在一个圆和一个更大的矩形之间进行动画。证明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
        }
    }
}

字符串


的数据
这就是我在视图之间转换时所追求的效果。

第二步:视图间切换

这里是事情变得复杂的地方。我们有两个不同的视图,紧凑版和全屏版。为了清楚起见,我把紧凑版做成了橙子,全屏版做成了红色,我是这样称呼它们的。
我们从前面的例子中删除了animatable属性,并将其替换为在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。它唯一做的就是动画大小。
我如何教SwiftUI相应地转换角半径?因此,两个视图都模仿彼此的行为,它将显示为一个视图。

yrdbyhpb

yrdbyhpb1#

你可以尝试这种方法,如示例代码所示。

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)
     }
 }

字符串

6za6bjd0

6za6bjd02#

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

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正在缩放视图,所以从绝对意义上说半径大小不匹配。您可以通过放弃使用matchedGeometryEffect并使用一个用于大小的状态变量来解决这个问题(就像角半径的状态变量一样)。使用此状态变量来控制maxWidth和maxHeight。
1.如果反转动画,插入的视图将不会对角设置动画。
如果你的意思是,切换if-else,那么确保cornerRadius初始化为正确的值。你还需要更改matchedGeometryEffect的标志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)
    }
}

im9ewurl

im9ewurl3#

几天后,我终于想出了一个有效的解决方案。

//  Created by Mark van Wijnen on 19/08/2023.

import SwiftUI

public struct CornerRadiusKey: EnvironmentKey {
    public static let defaultValue: Double = 0
}

extension EnvironmentValues {
    var cornerRadius: Double {
        get { return self[CornerRadiusKey.self] }
        set { self[CornerRadiusKey.self] = newValue }
    }
}

struct AnimatableRoundedRectangle: View {
    @Environment(\.cornerRadius) var cornerRadius: Double
    
    var body: some View {
        RoundedRectangle(cornerRadius: cornerRadius)
    }
}

struct AnimatableRoundedRectangleModifier: ViewModifier, Animatable {
    var cornerRadius: Double
    
    var animatableData: Double {
        get { cornerRadius }
        set { cornerRadius = newValue }
    }
    
    func body(content: Content) -> some View {
        return content
            .environment(\.cornerRadius, cornerRadius)
    }
}

extension AnyTransition {
    static func cornerRadius(identity: Double, active: Double) -> AnyTransition {
        AnyTransition.modifier(
            active: AnimatableRoundedRectangleModifier(cornerRadius: active),
            identity: AnimatableRoundedRectangleModifier(cornerRadius: identity)
        )
    }
}

struct ContentView: View {
    @Namespace var namespace
    
    @State private var isFullScreen = false
    
    var body: some View {
        VStack {
            if !isFullScreen {
                ZStack {
                    AnimatableRoundedRectangle()
                        .foregroundColor(.orange)
                        .matchedGeometryEffect(id: "card", in: namespace)
                        .frame(width: 100, height: 100)
                }
                .transition(.cornerRadius(identity: 60.0, active: 0.0))
            } else {
                ZStack {
                    AnimatableRoundedRectangle()
                        .foregroundColor(.orange)
                        .matchedGeometryEffect(id: "card", in: namespace)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
                .transition(.cornerRadius(identity: 0.0, active: 60.0))
            }
        }
        .animation(.linear, value: isFullScreen)
        .onTapGesture { isFullScreen.toggle() }
        .preferredColorScheme(.dark)
    }
}

#Preview {
    ContentView()
}

字符串


的数据

qrjkbowd

qrjkbowd4#

struct MainView: View {
    @State var toggle: Bool = false
    
    var body: some View {
        RoundedRectangle(cornerRadius: toggle ? 0 : 40)
        .fill(Color.red)
        .animation(.linear(duration: 1), value: toggle)
        .frame(width: 50, height: 50)
        .padding()
        .onTapGesture {
            withAnimation{ toggle.toggle() }
        }
    }
}

字符串


的数据

相关问题