用 UIViewPropertyAnimator 编写动画

[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 跳转的过渡动画。另外还有一些有趣的例子,例如一些简单的游戏。

时间: 2024-10-13 00:52:35

用 UIViewPropertyAnimator 编写动画的相关文章

android 编写动画

1.在编写动画的时候需要新建一个xml 新建的步骤是选中res单击右键选择Android resource file 然后弹出一个框 ,然后再Resource Type 里面选择Animation 然后再file name里面写一个xxx.xml就可以 在新建的xml里面写上这个个带红色的话 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.

(译)快速指南:用UIViewPropertyAnimator做动画

翻译自:QUICK GUIDE: ANIMATIONS WITH UIVIEWPROPERTYANIMATOR 译者:Haley_Wong iOS 10 带来了一大票有意思的新特性,像 UIViewPropertyAnimator,它是一个改善动画处理的全新的类. 这个视图属性动画完全颠覆了我们已经习惯的流程,能够为动画逻辑添加更精细的控制. 一个简单的动画 让我们来看看如何通过一个简单的动画改变视图的中心点属性. let animator = UIViewPropertyAnimator(du

WPF学习之绘图和动画

如今的软件市场,竞争已经进入白热化阶段,功能强.运算快.界面友好.Bug少.价格低都已经成为了必备条件.这还不算完,随着计算机的多媒体功能越来越强,软件的界面是否色彩亮丽.是否能通过动画.3D等效果是否吸引用户的眼球也已经成为衡量软件的标准. 软件项目成功的三个要素是:资源.成本.时间.无论是为了在竞争中保持不败还是为了激发起用户对软件的兴趣,提高软件界面的美化程度.恰当的将动画和3D等效果引入应用程序都是一个必然趋势.然而使用传统的桌面应用程序开发工具和框架(如Winform.MFC.VB.D

前端编程提高之旅(十六)————jquery中的动画

    上一篇文章对jquery中的事件做了总结,这篇文章主要对jquery中的动画做一下总结归类.最近微信端分享中,有很多页面交互及动画做的非常受欢迎,非常符合移动端体验.看似花哨的动画从本质上都脱离不了编写动画的基本方法.乐帝将jquery动画部分内容,做了一个简单的归类.     如下图:     如上图所示,无论多复杂的动画,从实现上都采用这些最底层的动画方法.本篇将从动画方法和与动画状态有关的方法讲起.    一.动画方法    1.同时改变高.宽.不透明度方法    这里涉及show

Android(java)学习笔记263:Android下的属性动画(Property Animation)

1. 属性动画(Property Animation)引入: 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系统在一开始的时候就给我们提供了两种实现动画效果的方式,逐帧动画(frame-by-frame animation)和补间动画(tweened animation). 逐帧动画的工作原理很简单,其实就是将一个完整的动画拆分成一张张单独的图片,然后再将它们连贯起来进行播放,类似于动画片的工作原理. 补间动画则是可以对View进行一系列的动画操作,包括淡入淡出.缩放.平移.

关于自定义转场动画,我都告诉你。

原文出处: 伯恩的遗产(@翁呀伟呀 ) 概述 这篇文章,我将讲述几种转场动画的自定义方式,并且每种方式附上一个示例,毕竟代码才是我们的语言,这样比较容易上手.其中主要有以下三种自定义方法,供大家参考: Push & Pop Modal Segue 前两种大家都很熟悉,第三种是 Stroyboard 中的拖线,属于 UIStoryboardSegue 类,通过继承这个类来自定义转场过程动画. Push & Pop 首先说一下 Push & Pop 这种转场的自定义,操作步骤如下: 创

unity3d动画操作以及动画实现

今天主要总结的是 unity3d 中内置动画操作,以及代码事件编写动画实现 1.如何导入.执行外部动画 在项目窗口中,首先,单击选择我们所准备的动画模型,在属性面板中选择Animations栏, 在属性中点击"+","-"可以增加和删除动画片段, 而在Start以及End中,可以分别设置每一个动画片段的开始帧数及结束帧数. 下面是我自己动画做的一个分解: Idle:表示我动画中的准备动作. Aim:表示我动画中的瞄准动作. Fire:表示我动作中的投篮动作. 当上面

android中xml设置Animation动画效果详解

在 android 中, Animation 动画效果的实现可以通过两种方式进行实现,一种是 tweened animation 渐变动画,另一种是 frame by frame animation 画面转换动画. tweened animation 渐变动画有以下两种类型: 1.alpha 渐变透明度动画效果 2.scale 渐变尺寸伸缩动画效果 frame by frame animation 画面转换动画有以下两种类型: 1.translate 画面转换位置移动动画效果 2.rotate

DirectUI通用动画框架

在编写VC界面时,编写动画比较困难,代码重用性不高.编写一个临时动画需要创建定时器或者线程来驱动改变渲染状态,来达到画面实时改变的目的.但是定时器和线程都是比较难以维护的,处理不好很容易造成资源浪费甚至程序崩溃. Skilla在上一周整理好了skillcore库,这一次又给它增添了通用动画框架.这个动画框架本身没有渲染功能,主要是提供动画的驱动事件,使用时需要自己去处理动画事件去完成动画渲染.该框架比较简单,动画由线程来驱动,下面展示一下具体的构成. 根据动画的特点,就像播放动画片一样,Skil