1

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

在此处输入图像描述

没什么特别的,只是背景中Text有一个Capsule。当用户打开它时,这个视图会有不同的外观。

在此处输入图像描述

当用户打开它时,MyCustomCapsuleViewpadding 应该增加到 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()
            }
        }
    }
}
4

1 回答 1

0

这有点棘手。目前,您.repeatForever(autoreverses: true)的动画不仅为 制作动画rotationEffect,还为padding. 为了防止这种情况并使用repeatForever only for rotationEffect,我认为要走的路是使用显式动画和 2 个单独@State的 s。

  • 显式动画让您指定要为哪些属性设置动画。
  • 2 个单独@State的 s 允许每个 var 使用不同的动画。
    • @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
        }
    }
})

结果:

动画符合预期,首先变大,然后围绕中心旋转

于 2021-08-05T05:15:46.533 回答