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

Swift中如何实现带easeIn启动和easeOut停止的无限旋转动画?

实现带缓入缓出效果的轮盘旋转动画

你需要的是**启动时加速(easeIn)、中途匀速、停止时减速(easeOut)**的完整旋转效果,递归调用线性UIView动画的方式无法灵活控制这三个阶段的过渡,推荐用「CADisplayLink+分段UIView动画」的方案来实现:

核心思路

  1. 启动阶段:用easeIn动画让轮盘从静止加速到匀速状态
  2. 匀速阶段:通过CADisplayLink(和屏幕帧率同步的定时器)持续更新旋转角度,保证匀速转动的流畅性
  3. 停止阶段:先终止CADisplayLink,再用easeOut动画让轮盘从匀速减速到静止

完整代码实现

首先定义必要的属性:

import UIKit

class RouletteViewController: UIViewController {
    @IBOutlet weak var imgViewRoulette: UIImageView!
    
    // 驱动匀速旋转的显示链接(和屏幕帧率同步)
    private var displayLink: CADisplayLink?
    // 累计旋转的总弧度(避免直接叠加transform导致精度丢失)
    private var totalRotation: CGFloat = 0.0
    // 标记是否处于匀速旋转状态
    private var isUniformRotating = false
    // 匀速角速度:每秒旋转4π弧度(即每秒2圈,可根据需求调整)
    private let uniformAngularSpeed: CGFloat = .pi * 4

然后实现启动旋转的方法:

// 启动轮盘旋转
    func startRouletteSpin() {
        guard !isUniformRotating else { return }
        
        // 1. 执行缓入加速动画(0.5秒内加速到匀速)
        let accelerationDuration: TimeInterval = 0.5
        let acceleratedRotation = uniformAngularSpeed * CGFloat(accelerationDuration)
        
        UIView.animate(withDuration: accelerationDuration, delay: 0, options: .curveEaseIn) {
            self.totalRotation += acceleratedRotation
            self.imgViewRoulette.transform = CGAffineTransform(rotationAngle: self.totalRotation)
        } completion: { [weak self] _ in
            guard let self = self else { return }
            // 2. 加速完成后,启动匀速旋转
            self.startUniformRotation()
        }
    }
    
    // 启动匀速旋转逻辑
    private func startUniformRotation() {
        isUniformRotating = true
        displayLink = CADisplayLink(target: self, selector: #selector(updateUniformRotation))
        displayLink?.add(to: .current, forMode: .common)
    }
    
    // 每帧更新匀速旋转角度
    @objc private func updateUniformRotation() {
        guard let link = displayLink else { return }
        // 根据帧率计算本次旋转的弧度增量
        let rotationDelta = uniformAngularSpeed * CGFloat(link.duration)
        totalRotation += rotationDelta
        imgViewRoulette.transform = CGAffineTransform(rotationAngle: totalRotation)
    }

最后实现停止旋转的方法:

// 停止轮盘旋转
    func stopRouletteSpin() {
        guard isUniformRotating else { return }
        
        // 1. 终止匀速旋转的displayLink
        displayLink?.invalidate()
        displayLink = nil
        isUniformRotating = false
        
        // 2. 执行缓出减速动画(0.5秒内从匀速减速到静止)
        let decelerationDuration: TimeInterval = 0.5
        let deceleratedRotation = uniformAngularSpeed * CGFloat(decelerationDuration)
        
        UIView.animate(withDuration: decelerationDuration, delay: 0, options: .curveEaseOut) {
            self.totalRotation += deceleratedRotation
            self.imgViewRoulette.transform = CGAffineTransform(rotationAngle: self.totalRotation)
        } completion: { _ in
            // 动画完成,轮盘完全停止
        }
    }
}

关键细节说明

  • 用totalRotation累计旋转角度,避免直接叠加transform.rotated(by:)导致的精度丢失问题
  • CADisplayLink和屏幕刷新率同步,比递归UIView动画的匀速效果更流畅,不会出现动画间隙
  • 缓入/缓出的时长、匀速角速度都可以根据需求调整,适配不同的轮盘大小和交互场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 07:13:01