如何用 Dance 实现可拖拽进度动画?交互式 scrubbing 动画完整实战
【免费下载链接】DanceA radical & elegant animation library for iOS.项目地址: https://gitcode.com/gh_mirrors/dance1/Dance
在 iOS 开发中,可拖拽进度动画是视频播放器、音乐 App、视频剪辑工具里最常见的交互形式——用户拖动进度条,画面实时跟随。Dance正是为此而生的 iOS 动画库:它基于 iOS 10 的UIViewPropertyAnimator,一行view.dance.animate { ... }即可创建动画,并能随时暂停、反向、scrubbing(拖动进度)预览。本文将以完整实战的方式,教你从零实现一个交互式 scrubbing 动画。
Dance 动画库是什么?为什么天生适合做拖拽进度动画?🎯
Dance 是一款radical & elegant的 iOS 动画库,把UIViewPropertyAnimator的强大能力封装成了极其优雅的链式 API。它的核心理念是:动画一旦创建,就"附着"在视图上,你可以在任何能引用到该视图的地方,随时控制这段动画。
circle.dance.animate(duration: 2.0, curve: .easeInOut) { $0.center = endPosition $0.backgroundColor = .systemBlue }.start()正是这种"动画可被外部引用控制"的设计,让 Dance 做 scrubbing 动画得心应手——进度条拖到哪,动画就渲染到哪,全程无缝衔接。核心实现只有单个文件,非常适合阅读:Dance.swift。
什么是 scrubbing 动画?拖拽进度动画的核心原理 🔍
Scrubbing 一词源自视频剪辑:像拖动播放器进度条一样,把动画"进度"拖到任意位置。在UIViewPropertyAnimator中,这对应fractionComplete属性(0 表示动画起点,1 表示终点)。
Dance 把它封装成了直观的progress属性(源码见 Dance.swift):
| 概念 | 说明 |
|---|---|
progress = 0 | 动画处于起始状态 |
progress = 0.5 | 动画正好进行到一半 |
progress = 1 | 动画到达终点 |
💡 理解这一点后,实现可拖拽进度动画就只剩"把手指/滑块的位置换算成 0~1 的数字"这一步了。
环境准备:最快安装 Dance 动画库的方法 ⚡
Dance 要求iOS 10+、Swift 3+,安装方式二选一:
方式一:CocoaPods(推荐)
platform :ios, '10.0' target 'YourProject' do use_frameworks! pod 'Dance' end方式二:直接拖入源码—— 将 Dance.swift 拖入 Xcode 工程,同样可用。
如果想把示例工程跑起来对照学习,也可以直接 clone 仓库https://gitcode.com/gh_mirrors/dance1/Dance,打开DanceExample.xcodeproj即可体验官方演示(一个圆形视图沿直线运动,用滑块实时拖拽进度)。
第一步:创建一段可被拖拽的动画 🎬
先创建一段"待命"的动画。注意:.animate {}只是定义动画,不会立刻播放:
import Dance // 用 UIKit 内置曲线(easeInOut / easeIn / easeOut / linear) circle.dance.animate(duration: 2.0, curve: .linear) { $0.center = endPosition }.addCompletion { position in print("动画结束位置:\(position)") } // 需要时再启动 circle.dance.start()Dance 还支持四种时序方式:UIKit 内置曲线、UISpringTimingParameters弹簧参数、自定义三次贝塞尔曲线、dampingRatio阻尼比,你可以在 README.md 的 Usage 章节找到全部写法。
第二步:核心代码——用 progress 实现交互式 scrubbing 🚀
创建好动画后,拖拽进度的核心代码只有一行:
// 方式一:直接给 progress 赋值 circle.dance.progress = 0.5 // 方式二:链式调用 setProgress(支持 Int / Float / Double / CGFloat) circle.dance.setProgress(to: 0.5)你可能好奇:如果动画还没启动,直接设置进度会怎样?Dance 做了很贴心的处理——在 Dance.swift 中,setFractionComplete会自动启动并立即暂停动画,然后才设置进度,确保动画始终能被拖拽。这就是 Dance 的 "forgiving"(宽容)哲学:替你兜住错误,绝不崩溃。
第三步:用 UISlider 驱动拖拽进度动画(完整实战)🎛️
现在把滑块接到动画上。在 Storyboard 中放一个取值范围0.0 ~ 1.0的UISlider,然后:
@IBAction func sliderValueChanged(_ sender: UISlider) { // slider.value 恰好是 0~1,直接映射为动画进度 circle.dance.setProgress(to: sender.value) }拖动滑块时,动画会实时、无缝地跟随到对应位置——一个可拖拽进度动画就完成了!官方示例工程就是这么实现的(见 ViewController.swift),它用了一个 10 秒的线性动画,滑块拖到哪儿,圆球就停在哪儿,效果非常直观。
第四步:暂停、反向与完成——动画生命周期全控制 🕹️
拖拽之外,Dance 还提供了完整的动画控制组合拳:
circle.dance.pause() // 暂停动画 circle.dance.reverse() // 像倒放视频一样反向播放 circle.dance.isReversed = true // 等价写法 circle.dance.finish(at: .current) // 结束动画,渲染在当前位置并触发完成回调配合addCompletion,你可以在动画结束(.end)、回到起点(.start)或停在当前进度(.current)时执行后续逻辑。例如拖拽松手后,让动画从当前进度继续播放到终点,就是播放器类 App 最常见的交互。
进阶:结合 UIPanGestureRecognizer 实现手指拖拽 scrubbing ✋
滑块之外,更"原生"的做法是用手势直接拖拽。核心思路是把手指位移换算成 0~1 的进度:
@objc func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) // 把 300pt 的位移映射为 0~1 的动画进度,并做边界裁剪 let progress = min(max(translation.x / 300, 0), 1) circle.dance.setProgress(to: progress) if gesture.state == .ended { // 松手后:进度超过一半则播完,否则倒放回起点 circle.dance.progress > 0.5 ? circle.dance.start() : circle.dance.reverse() } }这种"拖拽 + 松手回弹"的交互,正是视频剪辑、卡片滑动等场景的标配体验。
常见问题与调试技巧 🔧
- 控制不生效?先检查
circle.dance.hasAnimation是否为true——动画结束后,关联的 animator 会被移除,需要重新创建。 - 看不到报错崩溃?这是 Dance 的宽容设计。例如对没有动画的视图调用
.start(),它只会打印:** Dance Error: view with dance.tag = <tag> does not have an active animation! **,而不会闪退。 - 想追踪动画状态?用
circle.dance.state(.active/.inactive)、circle.dance.isRunning即可实时监控。
总结 ✨
借助 Dance 动画库,实现可拖拽进度动画只需三步:创建动画 → 把交互位置换算成 0~1 → 调用setProgress。相比手写CAAnimation或直接操作UIViewPropertyAnimator,Dance 的链式 API 让代码更简洁、更易维护,也让交互式 scrubbing 动画变得人人都能轻松上手。现在就打开示例工程 ViewController.swift 亲手试一试吧!
【免费下载链接】DanceA radical & elegant animation library for iOS.项目地址: https://gitcode.com/gh_mirrors/dance1/Dance
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考