iOS动画:UIViewPropertyAnimator进阶指南
1. UIViewPropertyAnimator基础要点
使用 UIViewPropertyAnimator 类型时,操作步骤如下:
1. 创建一个对象。
2. 为其添加动画和/或完成闭包。
3. 方便时启动动画。
将动画闭包包含在类实例中,通过动画工厂为动画重用提供了全新方法。创建视图关键帧动画时,仍需使用旧的API UIView.animateKeyframes(withDuration:delay:keyframes:) 。
2. 挑战任务
2.1 挑战1:将模糊动画提取到工厂
为了再次练习抽象动画,将 toggleBlur(_:) 中的模糊动画提取到 AnimatorFactory 的静态方法中。静态工厂方法应接受两个参数:要动画的视图以及是否动画到完全透明或完全不透明状态。最终,可使用以下单行代码轻松切换 blurView 的可见性:
func toggleBlur(_ blurred: Bool) {
AnimatorFactory.fade(view: blurView, visible: blurred)
}
这种方式展示了使用 UIViewPropertyAnimator 抽象和重用动画的便捷性。
2.2 挑战2:防止动画重叠
当重复点击同一图标时,图标会跳回初始状态,动画显得不流畅。为解决此问题,可按以下步骤操作:
1. 在 IconCell 类中添加一个可选属性 animator 。
2. 不再丢弃 AnimatorFactory.jiggle 的结果,而是将其存储在 animator 中。
3. 在 iconJiggle() 开始时,检查 animator 是否已设置,若已设置,检查其 isRunning 属性是否为 true 。
4. 若有正在运行的动画,直接从 iconJiggle() 返回,不创建新动画。
class IconCell {
var animator: UIViewPropertyAnimator?
func iconJiggle() {
if let animator = animator, animator.isRunning {
return
}
// 创建新动画的代码
}
}
3. 自定义动画计时
3.1 内置计时曲线
之前使用搜索栏时,会在小部件上淡入模糊视图。现在移除淡入动画,直接对模糊效果进行动画处理。操作步骤如下:
1. 打开 LockScreenViewController.swift ,添加新方法:
func blurAnimations(_ blurred: Bool) -> () -> Void {
return {
self.blurView.effect = blurred ? UIBlurEffect(style: .dark) : nil
self.tableView.transform = blurred ? CGAffineTransform(scaleX: 0.75, y: 0.75) : .identity
self.tableView.alpha = blurred ? 0.33 : 1.0
}
}
- 调整UI,在
viewDidLoad()中移除以下两行:
blurView.effect = UIBlurEffect(style: .dark)
blurView.alpha = 0
- 替换
toggleBlur(_:)的内容:
func toggleBlur(_ blurred: Bool) {
UIViewPropertyAnimator(
duration: 0.55,
curve: .easeOut,
animations: blurAnimations(blurred))
.startAnimation()
}
曲线参数 curve 的类型为 UIViewAnimationCurve ,包含四种内置曲线类型: .linear 、 .easeIn 、 .easeOut 和 .easeInOut 。可尝试不同曲线,观察动画变化。
3.2 自定义贝塞尔曲线
有时内置曲线无法满足精确的动画计时需求,此时可使用自定义贝塞尔曲线。贝塞尔曲线通过添加控制点来定义,三次贝塞尔曲线有两个控制点。Core Animation使用的三次曲线起点为 (0, 0) ,终点为 (1, 1) 。
可访问 http://cubic-bezier.com 网站,拖动控制点并预览动画效果。在 LockScreenViewController.swift 中,将 toggleBlur() 的现有动画替换为:
func toggleBlur(_ blurred: Bool) {
UIViewPropertyAnimator(duration: 0.55,
controlPoint1: CGPoint(x: 0.57, y: -0.4),
controlPoint2: CGPoint(x: 0.96, y: 0.87),
animations: blurAnimations(blurred))
.startAnimation()
}
这种方式可创建具有“弹性”的动画,但要注意避免过度使用导致动画效果滑稽。
4. 弹簧动画
UIViewPropertyAnimator 提供了用于定义弹簧驱动动画的便捷初始化方法 UIViewPropertyAnimator(duration:dampingRatio:animations:) ,它与 UIView 的 animate(withDuration: delay:usingSpringWithDamping: initialSpringVelocity: options: animations: completion:) 方法效果相同,初始速度为0。不过,这种方式是反向计算弹簧参数的,有更好的创建弹簧动画的方法。
5. 自定义计时提供者
UIViewPropertyAnimator 的 UIViewPropertyAnimator(duration:timingParameters:) 初始化方法可创建提供自定义计时数据的对象。 timingParameters 参数类型为 UITimingCurveProvider ,UIKit中有两个类符合该协议: UICubicTimingParameters 和 UISpringTimingParameters 。
5.1 提供阻尼和速度
可选择简单方式,仅提供阻尼比和初始速度:
let spring = UISpringTimingParameters(
dampingRatio: 0.5,
initialVelocity: CGVector(dx: 1.0, dy: 0.2))
let animator = UIViewPropertyAnimator(
duration: 1.0,
timingParameters: spring)
5.2 自定义弹簧
若想更精确地控制弹簧,可使用 UISpringTimingParameters 的另一个初始化方法指定弹簧的质量、刚度和阻尼:
let spring = UISpringTimingParameters(
mass: 10.0,
stiffness: 5.0,
damping: 30,
initialVelocity: CGVector(dx: 1.0, dy: 0.2))
let animator = UIViewPropertyAnimator(
duration: 1.0,
timingParameters: spring)
6. 自动布局动画
布局约束动画与使用 UIView.animate(withDuration:...). 创建的动画非常相似,关键是更新约束,然后在动画块中调用 layoutIfNeeded() 。
6.1 动画约束
在 AnimatorFactory.swift 中添加新的工厂方法:
@discardableResult
static func animateConstraint(
view: UIView,
constraint: NSLayoutConstraint,
by amount: CGFloat
) -> UIViewPropertyAnimator {
let spring = UISpringTimingParameters(dampingRatio: 0.2)
let animator = UIViewPropertyAnimator(
duration: 2.0,
timingParameters: spring)
animator.addAnimations {
constraint.constant += amount
view.layoutIfNeeded()
}
return animator
}
在 LockScreenViewController.swift 中,可按以下步骤操作:
1. 在 viewWillAppear(_:) 中移动日期标签:
dateTopConstraint.constant -= 100
view.layoutIfNeeded()
- 在
viewDidAppear(_:)中触发动画将标签移回原位:
AnimatorFactory.animateConstraint(
view: view,
constraint: dateTopConstraint,
by: 100)
.startAnimation()
可调整弹簧动画的参数,如将 dampingRatio 设置为0.55, duration 设置为1.0,使动画更微妙且有趣。
6.2 动画单元格高度变化
在 WidgetCell.swift 中,可按以下步骤动画单元格高度变化:
1. 重建 toggleShowMore(_:) 方法:
func toggleShowMore(_ sender: Any) {
self.showsMore.toggle()
let animations = {
self.widgetHeight.constant = self.showsMore ? 230 : 130
if let tableView = self.tableView {
tableView.beginUpdates()
tableView.endUpdates()
tableView.layoutIfNeeded()
}
}
let spring = UISpringTimingParameters(
mass: 30,
stiffness: 1000,
damping: 300,
initialVelocity: CGVector(dx: 5, dy: 0))
toggleHeightAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: spring)
toggleHeightAnimator?.addAnimations(animations)
toggleHeightAnimator?.startAnimation()
}
通过上述步骤,可实现各种复杂的动画效果,同时要注意动画的适度性,避免给用户带来不良体验。
以下是一个简单的流程图,展示了防止动画重叠的逻辑:
graph TD;
A[用户点击图标] --> B{animator是否已设置};
B -- 是 --> C{animator是否正在运行};
C -- 是 --> D[返回,不创建新动画];
C -- 否 --> E[创建新动画];
B -- 否 --> E[创建新动画];
表格展示不同动画初始化方法的特点:
| 初始化方法 | 特点 |
| — | — |
| UIViewPropertyAnimator(duration:curve:animations:) | 使用内置曲线 |
| UIViewPropertyAnimator(duration:controlPoint1:controlPoint2:animations:) | 使用自定义贝塞尔曲线 |
| UIViewPropertyAnimator(duration:dampingRatio:animations:) | 创建弹簧动画,反向计算弹簧参数 |
| UIViewPropertyAnimator(duration:timingParameters:) | 使用自定义计时提供者 |
iOS动画:UIViewPropertyAnimator进阶指南
7. 不同动画类型对比总结
为了更清晰地了解各种动画类型及其特点,我们将之前介绍的不同动画类型进行对比总结,以下表格展示了不同动画类型的关键信息:
| 动画类型 | 初始化方法 | 特点 | 适用场景 |
| — | — | — | — |
| 内置曲线动画 | UIViewPropertyAnimator(duration:curve:animations:) | 使用 .linear 、 .easeIn 、 .easeOut 和 .easeInOut 等内置曲线 | 一般的简单动画,对动画节奏有基本要求的场景 |
| 自定义贝塞尔曲线动画 | UIViewPropertyAnimator(duration:controlPoint1:controlPoint2:animations:) | 通过控制点定义自定义曲线,可实现特殊的动画节奏 | 需要精确控制动画计时,创造独特动画效果的场景 |
| 弹簧动画 | UIViewPropertyAnimator(duration:dampingRatio:animations:) | 产生类似弹簧的动画效果,反向计算弹簧参数 | 需要模拟弹性效果的动画场景 |
| 自定义计时提供者动画 | UIViewPropertyAnimator(duration:timingParameters:) | 可使用 UICubicTimingParameters 和 UISpringTimingParameters 等自定义计时参数 | 对动画计时有复杂需求,需要高度定制的场景 |
| 自动布局动画 | 结合 UIViewPropertyAnimator 和约束更新 | 通过更新约束并调用 layoutIfNeeded() 实现布局动画 | 涉及视图布局变化的动画场景 |
8. 动画性能优化建议
在使用 UIViewPropertyAnimator 创建动画时,为了确保动画的流畅性和性能,我们可以遵循以下优化建议:
1. 合理选择动画类型 :根据具体需求选择合适的动画类型,避免使用过于复杂的动画类型,以免影响性能。例如,对于简单的淡入淡出效果,使用内置曲线动画即可。
2. 控制动画时长 :避免过长的动画时长,过长的动画会让用户感到等待时间过长,影响用户体验。同时,过短的动画可能会让用户无法清晰感知,需要找到一个合适的平衡点。
3. 减少不必要的动画 :只对必要的视图和属性进行动画处理,避免对过多的视图进行动画,减少系统资源的消耗。
4. 使用硬件加速 :尽量使用可以利用硬件加速的动画属性,如 transform 和 alpha 等,这些属性的动画性能通常较好。
5. 避免在动画过程中进行大量计算 :在动画过程中进行大量的计算会导致动画卡顿,尽量将计算提前完成,或者在动画完成后进行计算。
9. 实际应用案例分析
以下是一个综合应用上述动画知识的实际案例,假设我们要实现一个带有动画效果的登录界面:
1. 界面布局 :界面包含用户名输入框、密码输入框、登录按钮和一个背景图片。
2. 动画设计 :
- 当界面加载时,背景图片从模糊到清晰,使用自定义贝塞尔曲线动画实现独特的过渡效果。
- 用户名和密码输入框从屏幕底部向上滑动进入,使用弹簧动画增加弹性效果。
- 登录按钮在用户点击时,先稍微缩小再恢复原状,使用内置曲线动画实现点击反馈。
以下是实现该案例的部分代码示例:
import UIKit
class LoginViewController: UIViewController {
@IBOutlet weak var backgroundImageView: UIImageView!
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// 初始化背景图片模糊效果
backgroundImageView.effect = UIBlurEffect(style: .dark)
// 背景图片动画
let backgroundAnimation = {
self.backgroundImageView.effect = nil
}
let backgroundAnimator = UIViewPropertyAnimator(duration: 1.0,
controlPoint1: CGPoint(x: 0.57, y: -0.4),
controlPoint2: CGPoint(x: 0.96, y: 0.87),
animations: backgroundAnimation)
backgroundAnimator.startAnimation()
// 输入框动画
let inputSpring = UISpringTimingParameters(dampingRatio: 0.2)
usernameTextField.transform = CGAffineTransform(translationX: 0, y: 200)
passwordTextField.transform = CGAffineTransform(translationX: 0, y: 200)
let inputAnimator = UIViewPropertyAnimator(duration: 1.0, timingParameters: inputSpring)
inputAnimator.addAnimations {
self.usernameTextField.transform = .identity
self.passwordTextField.transform = .identity
}
inputAnimator.startAnimation()
// 登录按钮点击动画
loginButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
}
@objc func buttonTapped() {
let buttonAnimation = {
self.loginButton.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
}
let buttonAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: buttonAnimation)
buttonAnimator.addCompletion { _ in
let restoreAnimation = {
self.loginButton.transform = .identity
}
let restoreAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeIn, animations: restoreAnimation)
restoreAnimator.startAnimation()
}
buttonAnimator.startAnimation()
}
}
10. 总结与展望
通过本文的介绍,我们深入了解了 UIViewPropertyAnimator 的各种用法,包括内置曲线动画、自定义贝塞尔曲线动画、弹簧动画、自定义计时提供者动画以及自动布局动画等。同时,我们还学习了动画性能优化的建议和实际应用案例。
在未来的开发中,我们可以根据具体的需求灵活运用这些动画技术,为用户创造更加生动、流畅和有趣的界面体验。随着技术的不断发展, UIViewPropertyAnimator 可能会有更多的功能和优化,我们可以持续关注并探索其新的用法。
以下是一个流程图,展示了动画性能优化的流程:
graph TD;
A[开始动画开发] --> B{选择动画类型};
B --> C{控制动画时长};
C --> D{减少不必要动画};
D --> E{使用硬件加速};
E --> F{避免大量计算};
F --> G[完成动画开发];
G --> H{测试动画性能};
H -- 性能达标 --> I[发布应用];
H -- 性能不达标 --> B;
通过以上的内容,我们对 UIViewPropertyAnimator 的使用有了更全面的认识,希望这些知识能帮助你在iOS开发中创造出优秀的动画效果。
超级会员免费看
5万+

被折叠的 条评论
为什么被折叠?



