在完全自动反转重复周期之间延迟 SwiftUI 中的重复动画

goh*_*tis 4 swift swiftui

我\xe2\x80\x99m 在 SwiftUI 中构建了一个 Apple Watch 应用,该应用读取用户\xe2\x80\x99 的心率并将其显示在心形符号旁边。

\n

我有一个动画可以让心形符号反复跳动。因为我知道实际的用户\xe2\x80\x99s心率,所以我\xe2\x80\x99d喜欢让它以与用户\xe2\x80\x99s心率相同的速率跳动,每次速率更新动画变化。

\n

我可以通过将心率除以 60 来确定节拍之间应间隔多少秒。例如,如果用户\xe2\x80\x99s 的心率为 80 BPM,则动画应每 0.75 秒 (60/80) 发生一次。

\n

这是我现在拥有的示例代码,其中currentBPM是一个常量,但通常会更新。

\n
struct SimpleBeatingView: View {\n    \n    // Once I get it working, this will come from a @Published Int that gets updated any time a new reading is avaliable.\n    let currentBPM: Int = 80\n    \n    @State private var isBeating = false\n    \n    private let maxScale: CGFloat = 0.8\n    \n    var beatingAnimation: Animation {\n        \n        // The length of one beat\n        let beatLength = 60 / Double(currentBPM)\n        \n        return Animation\n            .easeInOut(duration: beatLength)\n            .repeatForever()\n    }\n    \n    var body: some View {\n        Image(systemName: "heart.fill")\n            .font(.largeTitle)\n            .foregroundColor(.red)\n            .scaleEffect(isBeating ? 1 : maxScale)\n            .animation(beatingAnimation)\n            .onAppear {\n                self.isBeating = true\n            }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我希望让这个动画表现得更像苹果内置的心率应用程序。我不想让心脏不断变大或变小,而是让它跳动(两个方向的动画),然后暂停一会儿,然后再次跳动(两个方向的动画),然后再次暂停,依此类推。

\n

当我添加一秒延迟时,例如使用.delay(1)before .repeatForever(),动画会在每个节拍中暂停。例如,它变小,暂停,然后变大,然后暂停,等等。

\n

我明白为什么会发生这种情况,但是如何在每个自动反转重复之间插入延迟,而不是在自动反转重复的两端插入延迟?

\n

我有信心我可以计算出延迟应该多长以及每个节拍的长度以使一切正常运行的数学,因此延迟长度可以是任意的,但我正在寻找的是关于如何我的帮助可以实现动画循环之间的暂停

\n

我使用的一种方法是flatMap每次获得新的心率 BPM 时currentBPM都会重复已发布的Timer内容,这样我就可以尝试从中驱动动画,但我不确定如何才能将其转换为 SwiftUI 中的动画,并且我根据我目前对 SwiftUI 的理解,当时间似乎应该由动画处理时,我不确定以这种方式手动驱动值是否是正确的方法。

\n

paw*_*222 5

一种可能的解决方案是使用 链接单个动画片段DispatchQueue.main.asyncAfter。这使您可以控制何时延迟特定部分。

这是一个演示:

在此输入图像描述

struct SimpleBeatingView: View {
    @State private var isBeating = false
    @State private var heartState: HeartState = .normal

    @State private var beatLength: TimeInterval = 1
    @State private var beatDelay: TimeInterval = 3

    var body: some View {
        VStack {
            Image(systemName: "heart.fill")
                .imageScale(.large)
                .font(.largeTitle)
                .foregroundColor(.red)
                .scaleEffect(heartState.scale)
            Button("isBeating: \(String(isBeating))") {
                isBeating.toggle()
            }
            HStack {
                Text("beatLength")
                Slider(value: $beatLength, in: 0.25...2)
            }
            HStack {
                Text("beatDelay")
                Slider(value: $beatDelay, in: 0...5)
            }
        }
        .onChange(of: isBeating) { isBeating in
            if isBeating {
                startAnimation()
            } else {
                stopAnimation()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
private extension SimpleBeatingView {
    func startAnimation() {
        isBeating = true
        withAnimation(Animation.linear(duration: beatLength * 0.25)) {
            heartState = .large
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + beatLength * 0.25) {
            withAnimation(Animation.linear(duration: beatLength * 0.5)) {
                heartState = .small
            }
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + beatLength * 0.75) {
            withAnimation(Animation.linear(duration: beatLength * 0.25)) {
                heartState = .normal
            }
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + beatLength + beatDelay) {
            withAnimation {
                if isBeating {
                    startAnimation()
                }
            }
        }
    }

    func stopAnimation() {
        isBeating = false
    }
}
Run Code Online (Sandbox Code Playgroud)
enum HeartState {
    case small, normal, large

    var scale: CGFloat {
        switch self {
        case .small: return 0.5
        case .normal: return 0.75
        case .large: return 1
        }
    }
}
Run Code Online (Sandbox Code Playgroud)