iOS 10 :用 UIViewPropertyAnimator 编写动画
英文:shinobicontrols
译文:戴仓薯
[iOS 10 day by day] Day 1:开发 iMessage 的第三方插件
[iOS 10 day by day] Day 2:线程竞态检测工具 Thread Sanitizer
《iOS 10 day by day》是 shinobicontrols 公司编写的系列博客,介绍开发者需要了解的 iOS 10 新特性,每周更新。本系列翻译(文集地址)已取得官方授权。仓薯翻译,欢迎指正:)
Shinobicontrols 为 iOS 和 Android 开发者提供高性能、响应式的 UI 控件 SDK,尤其是图表方面的控件。 官网 : shinobicontrols.com twitter : @shinobicontrols
曾经的黑暗年代
用基于 block 的 UIView animation 来编写 view 属性(frame, transform 等等)变化的动画非常简单。只需要短短几行代码:
view.alpha = 1
UIView.animate(withDuration: 2) {
containerView.alpha = 0
}
你可以指定动画结束之后调用的 completion block。如果默认的匀速动画不能满足你的要求,还可以调整时间曲线。
但是,如果你需要一种自定义的曲线动画,相应的属性变化首先要快速开始,然后再急速慢下来,该怎么办呢?另外一个有点麻烦的问题是,怎么取消正在进行中的动画?虽然这些问题都可以解决,用第三方库或者创建一个新的 animation 来取代进行中的 animation。但苹果在 UIKit 中新加的组件能把这些步骤简化许多:进入UIViewPropertyAnimator的世界吧!
Animation 的新纪元
UIViewPropertyAnimator 的 API 设计得很完善,可扩展性也很好。它 cover 了传统 UIView animation 动画的绝大部分功能,并且大大增强了你对动画过程的掌控能力。具体来说,你可以在动画过程中任意时刻暂停,可以随后再选择继续,甚至还能在动画过程中动态改变动画的属性(例如,本来动画终点在屏幕左下角的,可以在动画过程中把终点改到右上角)。
为了探索这个新的类,我们来看几个例子,这几个例子都是演示一张图片划过屏幕的动画。如同所有 Day by Day 系列的文章,例子的代码可以在 Github 上下载到。这次我们用的是 Playground。
Playground 的准备
我们所有的 playground 页面都是让一个小忍者划过屏幕的动画。为了方便对比这些页面的代码,我们把公共部分的代码藏在 Sources 文件夹里。这样不仅能简化每个页面的代码,还能加快编译过程,因为 Sources 里的代码是预编译过的。
Sources 里包含一个简单的UIView子类,叫做NinjaContainerView。它的唯一功能就是添加一个 UIImageView 作为子 view,来显示我们的小忍者。我把忍者图片加到了 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” 功能,不用启动 iOS 模拟器就可以展示动画效果。尽管 Playground 还是有些不好用的地方,但用来尝试新功能是非常合适的。
要显示 Live View,点击菜单栏上的 View -> Assistant Editor -> Show Assistant Editor,或者点击右上角工具栏里两环相套的图标。如果在右半边的编辑器里没有看到 live view,要确保选中的是 Timeline 而不是 Manual —— 不得不承认我在这里浪费了一点时间。
从简单的开始
UIViewPropertyAnimator 的用法可以跟传统的 animation block 一样:
UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
containerView.moveNinjaToBottomRight()
}.startAnimation()
这会触发一个时长为 1 秒,时间曲线是缓进缓出的动画。动画的内容是闭包里的部分。
最简单的动画
注意我们是通过调用 startAnimation() 来显式启动动画的。另外一种创建 animator 的方法可以不用手动启动动画,就是 runningPropertyAnimator(withDuration:delay:options:animations:completion:)。确实有点长,所以可能还不如用第一种。
先创建好 animator ,再往上添加动画也很容易:
// view 设置好之后,我们先来一个简单的动画
let animator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut)
// 添加第一个 animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
// 然后再加第二个
animator.addAnimations {
ninja.alpha = 0
}
这两个 animation block 会同时进行。
两个 animation block
添加 completion block 的方法也很类似:
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 总体来说是很好用的,而且还能在 Live View 里面添加可交互的 UI 控件。然而,接受响应事件就有点麻烦,因为我们需要一个
NSObject
的子类来监听诸如.valueChanged
这种事件。所以,我们简单创建一个EventListener
,一旦触发它的handleEvent
方法,它会调用我们的eventFired
闭包。
这里 fractionComplete 值的计算方法跟时间没有关系了,所以我们的小忍者不再像之前指定的一样,会优雅地缓动。
Property animator 最强大的功能体现在它能随时打断正在进行的动画。让动画反向也非常容易,只需设置 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()
按下按钮的时候,animator 就会把动画反向进行,只要这一时刻动画还没结束。
自定义时间曲线
Property animator 在简洁优美的同时,还有很强的扩展性。如果你需要在苹果提供的时间函数之外自定义另一种时间曲线,只需传进一个实现 UITimingCurveProvider 协议的对象。大部分情况下用到的是 UICubicTimingParameters 或者 UISpringTimingParameters。
例如,我们想让小忍者在划过屏幕的过程中,先快速加速,然后再慢慢停止。如下图的贝塞尔曲线所示(绘制曲线用了这个很方便的在线工具):
http://cubic-bezier.com/#.17,.67,.83,.67
贝塞尔曲线
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()
扩展阅读
新的 property animator 让编写动画更简单,它的 API 跟传统方法类似,还添加了打断动画、自定义时间曲线等功能。
Apple 为 UIViewPropertyAnimator 提供了详尽的文档。另外,也可以看看这场 WWDC 视频,深度解读这些新的 API,还讲了怎么用新的 API 来做 viewController 跳转的过渡动画。另外还有一些有趣的例子,例如一些简单的游戏。
iOS 10 :用 UIViewPropertyAnimator 编写动画的更多相关文章
- iOS 10 的一个重要更新-用 UIViewPropertyAnimator 编写动画
曾经的黑暗年代 用基于 block 的 UIView animation 来编写 view 属性(frame, transform 等等)变化的动画非常简单.只需要短短几行代码: view.alpha ...
- 用 UIViewPropertyAnimator 编写动画
[iOS 10 day by day] Day 1:开发 iMessage 的第三方插件 [iOS 10 day by day] Day 2:线程竞态检测工具 Thread Sanitizer < ...
- 第三十二篇、iOS 10开发
1.语音识别 苹果官方在文档中新增了API Speech,那么在以前我们处理语音识别非常的繁琐甚至很多时候可能需要借助于第三方框架处理,那么苹果推出了这个后,我们以后处理起来就非常的方便了,spe ...
- (译)快速指南:用UIViewPropertyAnimator做动画
翻译自:QUICK GUIDE: ANIMATIONS WITH UIVIEWPROPERTYANIMATOR 译者:Haley_Wong iOS 10 带来了一大票有意思的新特性,像 UIViewP ...
- iOS开发UI篇—核心动画(UIView封装动画)
iOS开发UI篇—核心动画(UIView封装动画) 一.UIView动画(首尾) 1.简单说明 UIKit直接将动画集成到UIView类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画 ...
- iOS开发UI篇—核心动画(转场动画和组动画)
转自:http://www.cnblogs.com/wendingding/p/3801454.html iOS开发UI篇—核心动画(转场动画和组动画) 一.转场动画简单介绍 CAAnimation的 ...
- iOS开发UI篇—核心动画(关键帧动画)
转自:http://www.cnblogs.com/wendingding/p/3801330.html iOS开发UI篇—核心动画(关键帧动画) 一.简单介绍 是CApropertyAnimatio ...
- iOS开发UI篇—核心动画(基础动画)
转自:http://www.cnblogs.com/wendingding/p/3801157.html 文顶顶 最怕你一生碌碌无为 还安慰自己平凡可贵 iOS开发UI篇—核心动画(基础动画) iOS ...
- 使用 Swift 在 iOS 10 中集成 Siri —— SiriKit 教程
下载 Xcode 8,配置 iOS 10 和 Swift 3 (可选)通过命令行编译 除 非你想使用命令行编译,使用 Swift 3.0 的工具链并不需要对项目做任何改变.如果你想的话,打开 Xcod ...
随机推荐
- n维数组实现(可变参数表的使用)
首先先介绍一下可变参数表需要用到的宏: 头文件:#include<cstdarg> void va_start( va_list arg_ptr, prev_param ); type v ...
- 模板引擎Nvelocity实例
前言 最近一直忙于工作,没时间来管理博客,同时电脑也不给力,坏了一阵又一阵,最后还是去给修理了,这不刚一回来迫不及待的就写一篇文章来满足两个月未写博客的紧迫感. Nvelocity 关于nveloci ...
- 项目中遇到的Integer问题--转
Integer类型值相等或不等分析 http://www.cnblogs.com/zzllx/p/5778470.html 看到博客园一位博友写的面试问题,其中一题是 Integer a = 1; I ...
- 制作动画或小游戏——CreateJS事件(二)
在Canvas中如果要添加事件,就需要计算坐标来模拟各种事件,而EaselJS中已经封装好了多个事件,只需调用即可. 一.事件 1)点击 事件是绑定在Shape类中的,click事件与DOM中的意思是 ...
- HTML5 音频 <audio>
HTML5 提供了播放音频的标准. 一.Web 上的音频 直到现在,仍然不存在一项旨在网页上播放音频的标准. 今天,大多数音频是通过插件(比如 Flash)来播放的.然而,并非所有浏览器都拥有同样的插 ...
- Kooboo CMS 无聊随笔 (1)
因为公司的框架不开源,但是自己一直都有研究框架的兴趣,所以拿了一个开源的框架过来,而这个开源的框架就是Kooboo CMS.首先我无法用言语来形容我对这个CMS的赞美之词了,总之大家知道一点,这个CM ...
- CSS3知识点整理&&一些demo
css3能做什么 响应式开发的基础,然后能实现一些酷炫的效果咯. 以下案例纯css3实现,一点都没用js (入门简单,但是水很深) 叮当猫纯用css3做出 http://codepen ...
- Qt自适应大小显示图片,添加菜单
由于后面的图像处理需要UI,OpenCV自带也不怎么会,MFC实在懒得学的.听同学说Qt不错,就用Qt做UI了. 本文主要介绍三个内容:在Qt Creator中使用OpenCV2.Qt中自适应显示图片 ...
- 锁升级(Lock Escalations)——它们经常发生么?
前段时间,我写了一些SQL Server里锁升级的基础知识,还有它是如何影响执行计划的.今天,我想进一步谈下锁升级: 锁升级什么时候发生? 通常在SQL Server里如果在SQL语句里你请求的行数超 ...
- .Net(c#)模拟Http请求之HttpWebRequest封装
一.需求: 向某个服务发起请求获取数据,如:爬虫,采集. 二.步骤(HttpWebRequest): 无非在客户端Client(即程序)设置请求报文(如:Method,Content-Type,Age ...