iOS开发文集iOS 10 Day By Day:UIViewPropertyAnimator属性
基于UIView的动画block在动画属性(框架、变换,等等)之间添加过渡,轻松建立。他们创建起来是难以置信的容易,只需要几行代码:
view.alpha = 1
UIView.animate(withDuration: 2) {
containerView.alpha = 0
}
你还可以添加completion blocks,当动画完成时它们将被执行,如果默认的线性运动不能运行则调整动画曲线。
然而,如果你需要创建你自己的自定义动画曲线,就需要被动画迅速启动的属性,然后迅速减速,这时会发生什么呢?另一个稍微棘手的问题是你需要取消一个运行中的动画。当然这些可以通过使用第三方库或创建一个新的动画更换目前正在进行的动画来解决,苹果增加了一个新的组件到UIKit框架,使得这个问题解决起来变得容易许多:进入UIViewPropertyAnimator。
一个新的动画时代
UIViewPropertyAnimator是写得很好的且高度可扩展的API。它涵盖了许多与“旧式”UIView动画相同的功能,但能给你动画的精确编程控制。这意味着你可以暂停一个进度,在稍后的某个日期如果你愿意的话再启动它,甚至可以动态修改动画属性(比如将以前左下角的动画结束点位置改变到屏幕的右上方)。
为了探索这个新的类,我们将看几个我们在屏幕上制作的图像动画的例子。像所有的Day by Day博客一样,代码放在GitHub上(为了方便各位读者,小编已经为大家整理了,请点击这里下载)。这一次,我们要使用一个Playground。
Playground演练
我们所有的Playground页面都有一个忍者在屏幕上移动。为了使页面尽可能的贴切,我们将我们共同的代码隐藏在Sources文件夹里。这不仅是帮助你简化你的页面的代码,也使他们的运行速度同源被编译了一样快。
源包含一个简单的UIView子类,叫做NinjaContainerView。这个通过用显示我们的忍者的UIImageView子视图简单设置了视图。我已将图像添加到Playground的Resources组里。
import UIKit
public class NinjaContainerView: UIView {
public let ninja: UIImageView = {
let image = UIImage(named: "ninja")
let view = UIImageView(image: image)
view.frame = CGRect(x: 0, y: 0, width: 45, height: 39)
return view
}()
public override init(frame: CGRect) {
// Animating view
super.init(frame: frame)
// Position ninja in the bottom left of the view
ninja.center = {
let x = (frame.minX + ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
// Add image to the container
addSubview(ninja)
backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Moves the ninja view to the bottom right of its container, positioned just inside.
public func moveNinjaToBottomRight() {
ninja.center = {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
}
}
现在,在每个Playground页面,我们可以复制和粘贴以下代码:
import UIKit import PlaygroundSupport // Container for our animating view let containerView = NinjaContainerView(frame: CGRect(x: 0, y: 0, width: 400, height: 400)) let ninja = containerView.ninja // Show the container view in the Assistant Editor PlaygroundPage.current.liveView = containerView
这将使用Playground有用的“Live View”功能,无需启动模拟器就可以给我们一个我们的动画的可视化演示。他们仍然有自己的方式,但Playgrounds是尝试新东西的明智之举。
要显示Live View窗格,导航到View > Assistant Editor > Show Assistant Editor,或者,选择在工具栏中右上方看起来像两个相交圆的图标。如果你在Assistant Editor中没有看到Live View,要确保已选择了Timeline,而不是Manual,比起我认识到这一点,我已经失去了更多的时间!
从简单的开始
我们可以用同旧式的基于block的API完全相同的方式使用UIViewPropertyAnimator :
UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
containerView.moveNinjaToBottomRight()
}.startAnimation()
这将启动一个持续1秒的动画,有一个简化后的时间曲线。执行的动画在关闭中。

请注意,我们必须通过调用startAnimation()明确启动动画。另一种方法来创建无需自身启动的动画是使用runningPropertyAnimator(withDuration:delay:options:animations:completion:)——一个公平的mouthful,所以你可能更喜欢使用手动启动版。
在动画已经创建之后,添加额外的动画是很容易的。
// Now we've set up our view, let's animate it with a simple animation
let animator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut)
// Add our first animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
// Now here goes our second
animator.addAnimations {
ninja.alpha = 0
}
这些动画blocks会跑在一起。

Completion blocks可以以类似的方式添加:
animator.addCompletion {
_ in
print("Animation completed")
}
animator.addCompletion {
position in
switch position {
case .end: print("Completion handler called at end of animation")
case .current: print("Completion handler called mid-way through animation")
case .start: print("Completion handler called at start of animation")
}
}
在动画被允许运行它的整个持续时间的情况下,我们将看到以下版块:
Animation completed Completion handler called at end of animation
滑动和反向
我们可以使用animator来滑动我们的动画:
let animator = UIViewPropertyAnimator(duration: 5, curve: .easeIn)
// Add our first animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
let scrubber = UISlider(frame: CGRect(x: 0, y: 0, width: containerView.frame.width, height: 50))
containerView.addSubview(scrubber)
let eventListener = EventListener()
eventListener.eventFired = {
animator.fractionComplete = CGFloat(scrubber.value)
}
scrubber.addTarget(eventListener, action: #selector(EventListener.handleEvent), for: .valueChanged)

Playground是很棒的,你甚至可以添加交互式UI元素到Live View中。不幸的是,接收事件是痛苦的,因为我们需要一个符合NSObject可以监听事件的类,像是.valueChanged。为此,当handleEvent方法被调用时,我们用一个简单的对象EventHandler调用我们的eventFired关闭。
分数与时间无关,所以我们看不到我们的忍者在一个像我们定义的优雅的easeIn曲线上移动。
属性动画的真正力量来自于能够中断正在进行的动画的能力。我们可以通过简单的切换isReversed属性来反向移动动画。
为了说明这一点,我们使用关键帧动画,这样我们就可以定义一个多级动画:
animator.addAnimations {
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
ninja.center = containerView.center
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
containerView.moveNinjaToBottomRight()
}
})
}
let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 30)))
button.setTitle("Reverse", for: .normal)
button.setTitleColor(.black(), for: .normal)
button.setTitleColor(.gray(), for: .highlighted)
let listener = EventListener()
listener.eventFired = {
animator.isReversed = true
}
button.addTarget(listener, action: #selector(EventListener.handleEvent), for: .touchUpInside)
containerView.addSubview(button)
animator.startAnimation()
当按下按钮,属性动画将反转动画,如果其状态是激活的(即动画目前在运行中,忍者尚未到达其最终目的地)。

添加你自己的时间曲线
属性动画是很简单的,其余是漂亮的扩展。如果你需要另一种苹果所提供的动画曲线,你可以通过你自己符合UITimingCurveProvider协议的情况。在大多数情况下,你可能会使用UICubicTimingParameters或UISpringTimingParameters。
我们说我们的忍者加速真的很快,然后在屏幕上他们的旅途中出现一个渐进的停止。我们将使用Bezier曲线,如下图(使用这个方便的在线工具绘制)。

let bezierParams = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.05, y: 0.95),
controlPoint2: CGPoint(x: 0.15, y: 0.95))
let animator = UIViewPropertyAnimator(duration: 4, timingParameters:bezierParams)
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
animator.startAnimation()

进一步的阅读
新的属性动画通过带来在一致的API下的现有能力,看起来会让动画项目比以往任何时候都容易,增加了中断动画和你自己的自定义时序曲线的能力。
苹果提供了一些优秀的UIViewPropertyAnimator文件。或者,你可以观看WWDC演讲,将在新的API进行深入研究和探讨可以用来如何创建UIViewControllers之间的自定义转换。其他有趣的演示也包括了简单的游戏。
本文翻译自:

QQ交谈
在线咨询

渝公网安备
50010702500608号

客服热线