【问题标题】:SwiftUI: Unexpected Animation when using a non @State varSwiftUI:使用非 @State var 时出现意外动画
【发布时间】:2021-08-05 01:43:19
【问题描述】:

我需要一些帮助。我创建了以下可重用视图,我们称之为MyCustomCapsuleView

没什么特别的,只是一个Text 和一个Capsule 在后台。当用户打开此视图时,它会有不同的外观。

当用户打开它时,MyCustomCapsuleView 填充应增加到 17,以解决新外观所需的额外空间。此外,它应该:

  1. 放大和缩小
  2. 开始摆动

当用户关闭它时,MyCustomCapsuleView 填充应该回到 3,因为不再需要额外的空间。此外,它应该:

  1. 停止摆动
  2. 再次放大和缩小

我能够获得此功能的版本,但填充无法正常工作。当MyCustomCapsuleView 摆动时,填充在 3 到 17 之间来回移动(参见黄色背景)。

  1. 任何想法我做错了什么?我怎样才能实现我需要的行为?注意:由于这个视图是可重用的,我需要我的视图的“关闭”版本保持填充 = 3。我将在我的应用程序的不同部分使用它,并且仅在某些地方“打开/关闭”版本将是可用,并且仅在这些地方,我的视图的填充是 17。

  2. 为什么旋转(摆动效果)看起来如此奇怪?我尝试了不同的锚单元点(中心、尾随等),但它们似乎都没有让我好看。

感谢您的帮助!

这是我的代码:

struct MyCustomCapsuleView: View {
    @State private var scaleUp = false
    
    var showRemove: Bool
    private var fillColor: Color { return Color(#colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)) }
    private var foregroundColor: Color { return Color(#colorLiteral(red: 0.1019607857, green: 0.2784313858, blue: 0.400000006, alpha: 1)) }
    
    var body: some View {
        ZStack(alignment: .topTrailing) {
            Text("Hello World!")
                .foregroundColor(foregroundColor)
                .padding(.horizontal)
                .padding(.vertical, 10)
                .lineLimit(1)
                .background(
                    ZStack {
                        Capsule(style: RoundedCornerStyle.continuous)
                            .fill(fillColor)
                        Capsule(style: RoundedCornerStyle.continuous)
                            .stroke(foregroundColor, lineWidth: 2)
                    }
                )
            
            Circle()
                .fill(Color.red)
                .frame(width: 30, height: 30)
                .overlay(
                    Image(systemName: "multiply")
                        .resizable()
                        .padding(7)
                        .foregroundColor(.white)
                )
                .offset(x: 15, y: -15)
                .scaleEffect(showRemove ? 1 : 0, anchor: UnitPoint.topTrailing)
                .animation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0), value: showRemove)
        }
        .padding(showRemove ? 17: 3)
        .background(Color.yellow)
        .scaleEffect(scaleUp ? 1.2 : 1)
        .animation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0), value: scaleUp)
        .rotationEffect(.degrees(showRemove ? 7 : 0), anchor: UnitPoint(x: 0.10, y: 0.0))
        .animation(showRemove ? .easeInOut(duration: 1.15).repeatForever(autoreverses: true).delay(0.3) : .easeInOut(duration: 0.15), value: showRemove)
        .onChange(of: showRemove, perform: { value in
            if !scaleUp {
                scaleUp = true
                Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
                    scaleUp = false
                }
            }
        })
    }
}

struct Test: View {
    @State var showClose = false
    
    var body: some View {
        HStack {
            MyCustomCapsuleView(showRemove: showClose)
            Button("Turn On/Off") {
                showClose.toggle()
            }
        }
    }
}

【问题讨论】:

    标签: swift swiftui swiftui-animation


    【解决方案1】:

    这有点棘手。目前,您的.repeatForever(autoreverses: true) 动画不仅为rotationEffect 设置动画,还为padding 设置动画。为了防止这种情况发生并为rotationEffect 使用repeatForever only,我认为要走的路是explicit animations 和2​​ 个单独的@States。

    • 显式动画可让您指定要为哪些属性设置动画。
    • 2 个独立的@States 允许为每个变量使用不同的动画。
      • @State var showClose 是一次性动画,用于创建更大的padding() 和放大动画
      • @State var rotatingrepeatForever 的旋转动画

    这是您的 Test 结构的样子:

    struct Test: View {
        @State var showClose = false
        @State var rotating = false
        
        var body: some View {
            HStack {
                MyCustomCapsuleView(showRemove: showClose, rotating: rotating)
                
                Button("Turn On/Off") {
                    print(showClose)
                    withAnimation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0)) {
                        showClose.toggle()
                    }
                    withAnimation(rotating ? .easeInOut(duration: 0.15) : .easeInOut(duration: 1.15).repeatForever(autoreverses: true).delay(0.3)) {
                        rotating.toggle()
                    }
                }
            }
        }
    }
    

    然后,修改MyCustomCapsuleView 中的这些行:

    var showRemove: Bool
    var rotating: Bool /// add this
    
    /// replace your `ZStack`'s modifiers with this:
    .padding(showRemove ? 17: 3)
    .background(Color.yellow)
    .scaleEffect(scaleUp ? 1.2 : 1)
    .rotationEffect(.degrees(rotating ? 7 : 0))
    .onChange(of: showRemove, perform: { value in
        if !scaleUp {
            scaleUp = true
    
            /// btw, why not just `DispatchQueue.main.asyncAfter(.now() + 0.1)`?
            Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
                scaleUp = false
            }
        }
    })
    

    结果:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      • 2021-01-09
      • 2019-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多