You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SwiftUI中按定时周期交替切换三种不同动画?

多阶段可变时长动画序列的最优实现方案

针对你需要实现的A/B/C交替循环、A时长递增、支持中途取消的动画需求,这里提供一套基于ObservableObject+DispatchWorkItem的结构化实现方案,比零散使用WorkItem更易维护和扩展。

核心思路

  1. 明确阶段状态:用枚举定义动画的不同阶段(空闲、显示A、显示B、显示C),避免单一布尔值带来的状态模糊问题
  2. 动态调度任务:通过递归调度每个阶段的结束事件,自动切换到下一个阶段,同时在C阶段完成后递增A的时长
  3. 可取消机制:用DispatchWorkItem管理每个阶段的延迟任务,停止时直接取消pending任务并重置状态

ViewModel实现(核心逻辑)

ViewModel负责管理动画状态、计算时长、调度阶段切换,所有状态通过@Published通知View更新:

import Foundation
import Combine

class AnimationSequenceViewModel: ObservableObject {
    // 定义动画阶段
    enum AnimationPhase {
        case idle
        case showA
        case showB
        case showC
    }
    
    @Published var currentPhase: AnimationPhase = .idle
    @Published var isRunning: Bool = false
    
    private var currentADuration: TimeInterval = 30 // A初始时长
    private let fixedBDuration = 10.0
    private let fixedCDuration = 10.0
    private var currentWorkItem: DispatchWorkItem?
    private let taskQueue = DispatchQueue(label: "com.animation.sequence.queue")
    
    // 启动动画序列
    func startSequence() {
        guard !isRunning else { return }
        isRunning = true
        currentADuration = 30 // 重置初始时长
        scheduleNextPhase(.showA)
    }
    
    // 停止动画序列
    func stopSequence() {
        isRunning = false
        currentWorkItem?.cancel()
        currentWorkItem = nil
        currentPhase = .idle
    }
    
    // 递归调度下一个阶段
    private func scheduleNextPhase(_ targetPhase: AnimationPhase) {
        guard isRunning else { return }
        
        currentPhase = targetPhase
        
        // 计算当前阶段时长和下一个阶段
        let duration: TimeInterval
        let nextPhase: AnimationPhase
        
        switch targetPhase {
        case .showA:
            duration = currentADuration
            nextPhase = .showB
        case .showB:
            duration = fixedBDuration
            nextPhase = .showC
        case .showC:
            duration = fixedCDuration
            currentADuration += 10 // 下一轮A时长+10秒
            nextPhase = .showA
        case .idle:
            return
        }
        
        // 创建并调度任务
        let workItem = DispatchWorkItem { [weak self] in
            self?.scheduleNextPhase(nextPhase)
        }
        currentWorkItem = workItem
        taskQueue.asyncAfter(deadline: .now() + duration, execute: workItem)
    }
}

View层适配

View根据ViewModel的currentPhase状态控制每个动画的显示/隐藏和动画效果,确保动画时长与阶段时长同步:

import SwiftUI

struct AnimationSequenceView: View {
    @StateObject private var viewModel = AnimationSequenceViewModel()
    
    var body: some View {
        VStack {
            ZStack {
                // 动画A:圆形淡入淡出
                Circle()
                    .fill(.blue)
                    .frame(width: 100, height: 100)
                    .opacity(viewModel.currentPhase == .showA ? 1 : 0)
                    .animation(.easeInOut(duration: viewModel.currentADuration), value: viewModel.currentPhase)
                
                // 动画B:三角形偏移
                Triangle()
                    .fill(.red)
                    .frame(width: 100, height: 100)
                    .offset(
                        x: viewModel.currentPhase == .showB ? 100 : 0,
                        y: viewModel.currentPhase == .showB ? 100 : 0
                    )
                    .animation(.easeInOut(duration: viewModel.fixedBDuration), value: viewModel.currentPhase)
                
                // 动画C:三角形缩放
                Triangle()
                    .fill(.green)
                    .frame(
                        width: viewModel.currentPhase == .showC ? 100 : 0,
                        height: viewModel.currentPhase == .showC ? 100 : 0
                    )
                    .animation(.easeInOut(duration: viewModel.fixedCDuration), value: viewModel.currentPhase)
            }
            .padding()
            
            // 控制按钮
            Button(action: {
                viewModel.isRunning ? viewModel.stopSequence() : viewModel.startSequence()
            }) {
                Text(viewModel.isRunning ? "停止动画序列" : "启动动画序列")
                    .padding()
                    .background(.black)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
        }
    }
}

// 自定义三角形形状
struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: rect.midX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.closeSubpath()
        return path
    }
}

关键细节说明

  • 状态隔离:ViewModel完全接管动画逻辑,View只负责UI渲染,符合MVVM架构
  • 取消安全性:停止时取消当前pending的DispatchWorkItem,避免任务继续执行导致状态异常
  • 动画同步:每个动画的时长直接绑定到对应阶段的时长,确保动画播放时长与阶段停留时长一致
  • 扩展性:如果后续需要修改时长规则或新增动画阶段,只需修改AnimationPhase枚举和调度逻辑即可

内容的提问来源于stack exchange,提问作者ProgrammerTrying

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 08:18:13