如何制作 Uber 启动开屏界面

Derek Selander 将 Uber 动画一步步拆解,利用遮罩,向量计算,组合等多种方式, 重新将动画建立起来。

2016.09.26更新:此教程已使用Xcode8 和Swift3进行更新.

Uber-feature

Oh,启动动画­­­—-当应用忙碌调用API端口获取功能所需的重要数据时,开发者们可以抓住这个时机疯狂展示有趣的动画。启动动画(不要与静态的,非动画应用开始画面混淆)在应用中承担重要的角色:在应用等待启动时刻保持对用户的吸引力。

虽然有很多启动动画的例子,但你很难找到一个像Uber如此漂亮的启动动画。在2016年第一季度,Uber发布了由其CEO领导的品牌再造战略。该战略成果之一就是一个炫酷的启动画面。

这个教程目的在于使用十分近似的方法复制Uber的启动画面。该方法大量使用CALayer和CAAnimation以及它们的子类集。相比介绍在这些类中发现的设计思想方法,此教程更集中于这些类在高质量动画上的使用。你可以查看Marin Todorov’s Intermediate iOS Animation video series去学习这些动画背后的设计思想。

开始

因为在这个教程中有很多有意义的动画需要实现完成,你将会以一个项目开始,这个项目已经包含了接下来制作优美动画所需的CALayer包。在这下载开始项目

“开始项目”为一个名叫Fuber的应用。Fuber是一个请求式交通分享服务软件,允许乘客请求Segway司机将他们载往城市不同的地方。Fuber增长十分迅速,已经在60多个国家为乘客们服务,但同时它也面临着多个国家政府以及Segway联盟的反对。

Splash screen

在此教程结尾时,你将会制作出一个像下面的启动动画:

Fuber Animation

 

打开并运行Fuber启动项目并浏览

以UIViewControler的角度来看,应用通过其父视图控制类RootContainerViewController来启动SplashViewController,该父视图的控制可以改变子视图的控制(UIViewController)。它循环播放启动动画直到应用启动。当应用和API端点信息交换或者应用需要必要的数据继续传送时,启动画面就会循环播放。  值得一提的是,在这个示例工程中启动画面在它本身的组件内. 在RootContainerViewController内showSplashViewController()和showSplashViewontrollerNoPing() 两个函数可以用来执行画面控制。教程中,大多数情况下,showSplashViewControllerNoPing()函数只会在动画循环时调用,因此只需要专注SplashViewController内的子视图动画,接着先使用showSplashViewController()去模拟API延时,然后转到动画主控制器上。

启动画面视图图层的合成

SplashViewController视图包含两个子视图。第一个子视图由“波纹网格”为背景,作为TileGridView(贴片网格视图),其包括一个网格状布局的TileView(贴片状视图)子视图实体。另一个子视图由‘U’图标动画组成,作为AnimatedULogoView(该应用logo中U动画视图)。

AnimatedULogoView包含四个CAShapeLayers:

  • CircleLayer代表‘U’的白色圆形背景
  • lineLayer是从circlelayer布局的中心出发到其边界的直线
  • squareLayer是在circleLayer层中心的方形
  • maskLayer被用于视图的遮罩。当边界跟随动画变化时,被用于在一个简单动画中折叠其他的视图层。

 

通过组合,这些CAShaperLayer合成Fuber应用中的‘U’。

RiderIconView

现在已经知道这些层是如何组成的,那接下来应该去生成动画使AnimateULogoView动起来。

为圆添加动画

当制作动画时,最好先评估视觉噪声并关注动画当前运行情况。 转到AimatedULogoView.swift文件,在init(frame:)内,把除了circleLayer层的其他所有被添加到视图的子视图层注释掉。当完成该动画时,可以将它们一个一个从新添加回去。代码应该如下:

 

找到generateCircleLayer()函数去理解圆形是如何制作出来的。它是使用UIBexierPath 绘画出的一个简单的CAShapeLayer。仔细看这一行代码:

在默认情况下,将0作为起始角度(startAngle),贝塞尔曲线路径从顺时针3点钟方向开始。 设定-M_PI_2的值为-90度,从圆的顶部开始到270度角处结束或者在3*M_PI_2角度处重新回到圆的顶部。因为需要实现整个圆形形成动画,所以需要特别关注那些(参数的设定),同时将圆的半径大小作为线的宽度(lineWidth)。
转到annimateCircleLayer()函数占位符处并添加如下代码:circleLayer动画需要包含三个CAAnimaiton:一个CAKeyframeAnimation作为动画行程的终点,一个CABasicAnimation作为动画转换,还有一个CAAnimationGroup用于将这两者组合在一起。你将会同时创建这些动画。

通过设定CAKeyframeAnimation动画起始和结束值为 0.0和1.0,就告诉动画核心框架从起始角开始画圆到结束角处结束,就像时钟转动动画。随着strokeEnd的值增加,线的长度会沿着圆周增加,圆逐渐被充满 。如果将参数值设定为[0.0,0.5],那么只会画出半个圆,因为strokeEnd将只会到达圆的半周。

现在增加转换动画:

这个动画既包含尺度变换同时包括绕‘Z’轴的旋转变换。这导致当旋转45度角时circleLayer会发生改变。这些旋转很重要,因为当与其他图层一起形成动画时,需要与lineLayer的位置和转动速度相匹配。最后,在animateCircleLayer()函数底部增加一个CAAnimationGroup。这个动画将前两个动画组合在一起,这样就可以在circleLayer层中只添加一个动画。

CAAnimationGroup有两个显著的特性被更改了:beginTime和timeOffset。如果你对它们中的任意一个都不是很熟悉,你可以在这找到这些特性的描述及用法说明.

groupAnimation的beginTime参考它的父视图进行设定。

时间补偿(timeOffset)是需要的,因为动画第一次运行时实际上会从动画的一半处开始。如果有更多完整的动画,可以试着改变startTimeOffset的值并观察视觉上的不同。

添加groupAnimation到circleLayer,然后构建并运行应用去看看动画是什么样子。

Splash Screen CircleIn Animation

提示:试一试在groupAnimation动画组中移除strokeEndAnimation或者transformAnimation 去弄明白每个动画的作用。 把在这个教程中你所制作的动画都像这样试一试,你将会惊奇于动画组合产生的让人意想不到的特殊视觉效果。

让线动起来

已经完成了circleLayer动画,接下来处理lineLayer的动画。同样还在AnimatedULogoView.swif文件中,转到startAnimating()函数并将除animateLineLayer()外的其它函数都注释掉。结果如以下代码:

另外,更改init(frame:)中的内容,使circleLayer和lineLayer是唯一正在被使用的CALayers层:

正确注释掉CALayers 动画,转到animateLineLayer()函数并执行下一组动画:

这个动画实现lineLayer宽度先增加后变小的变换

对下个动画,添加下列代码:

用CAAnimationGroup将动画组合并添加到lineLayer:同circleLayer变换动画类似,定义一个绕Z轴顺时针的旋转。对于线条,同时执行一个25%的缩放变换,然后恢复,最后收缩到15%。


构建并运行程序,你将看到下面漂亮的画面

Splash Screen Knockoutline Animation

注意使用相同的命名方法,用-M_PI_4初始化排列线条和圆周行程的转换参数。同样用 [0.0, 1.0-kAnimationDurationDelay/kAnimationDuration, 1.0]来设定keyTimes。数组中第一个和最后一个元素的意义很明显:0代表开始,1.0代表结束,因此也可以得到开始到圆的动画结束,第二部分(线条收缩)动画出现之间任意想要计算的值。

因为需要在动画播放结束时再回到播放延迟的时间点(为循环播放),因此用1减去kAnimationDurationDelay和 kAnimationDuration的比值(获取延迟的时间点)

现在已经完成circleLayer和lineLayer动画,接下来转到中心的方形动画。

让方形动起来

现在应该很熟练了,和之前一样。转到startAnimating()函数并注释掉除animateSquareLayer()外的函数。像下面这样更改init(frame:)函数:

上述工作完成后,转到animateSquareLayer()函数并实现下面动画:

这个动画改变CALayer的大小,是一个实现将方形边长缩小到三分之二,然后恢复,最后变成0的关键帧动画.

继续为背景色添加动画效果:

注意fillMode属性。即使beginTime不是0,背景色也会在动画开始前保持动画开始时的CGColor,在动画结束后保持动画结束时的CGColor。这会使得当将这些动画被添加到父CAAnimationGroup时不产生闪烁。

说到这里,接下来就来实现它吧:

构建并运行。注意方形的动画效果。

Splash Screen Tutorial

现在可以结合前面的动画看看整个效果。

提示:模拟器中的动画可能会出现参差不齐不完整,这因为仿真只是在GPU上模仿IOS设备的虚拟环境中进行的。如果你的电脑带不动动画,可以将模拟器显示窗口调小或直接运行在ios设备上。

遮罩

首先,取消所有在init(frame:)中增加的和动画startAnimating()函数中的注释。

在所有动画组合下,构建并运行Fuber

PreMask Animation

动画还有一点不足,当圆消失时,圆的尺寸会产生突变的感觉。幸运的是,遮罩动画可以修复这个缺陷,使各子层动画收缩平滑。

转到animateMaskLineLayer()函数并添加下列代码:

这是平滑边界变化的动画。记住当边界改变时,整个AnimatedULogoView将会消失直到该层的遮罩作用于所有的子动画层。

现在执行一个圆角动画 使遮罩保值圆形:


把这两个动画添加到一个CAAnimationGroup去完成这个动画层:

构建并运行

RiderIconView Animation

看起来不错哦!

网格

数字边界。试着想象成串的UIViews穿过TileGridView的情景。那会是什么样子呢?

好…可以参考Tron这部电影去看看是什么样子!

背景网格由一系列添加到父类TileGridView的TileView构成。为了能更快了理解这一点,打开TileView.swift文件并找到init(frame:)函数。在最底部添加如下属性:

 

构建并运行该应用

Fuber-Grid-View

正如所见,TileView 被排在一个网格里。

所有这些逻辑被创建在TileGridView.swift中renderTileView()函数内。 所幸的是,方格布局的这些逻辑在开始项目中实现了。因此你所要做的就是让它动起来。

给TileView添加动画

TileGridView有一个唯一的子视图containerView。它会添加所有的子 TileView。另外,它还有一个 tileViewRows的属性,该属性是一个包含所有添加到containerView的 TileView二维矩阵。返回到TileView的init(frame:)函数。删除为了显示边界而增加的边框,并将chimeSplashImage前的注释去掉,将它添加到图层。函数如下:

构建并运行

Grid Starting

然而, TileGridView (和所有的TileView)也需要添加一些动画。打开TileView.swift,转到startAnimatingWithDuration(_:beginTime:rippleDelay:rippleOffset:)并完成下一块的动画:

这段代码设置了一系列的定时函数,这些函数不久将会被用到。添加如下代码:

shouldEnableRipple 是一个布尔类型变量,它决定变换和方位动画什么时候会被添加到动画序列中。对所有不在网格边界的TileView,shouldEnableRipple的值被设为True,。这一逻辑已经随TileGridView在renderTileView()中被创建时实现了。

添加透明动画:

这个动画简单明了,但包含了一些特殊的keyTimes。

现在把所有的动画添加到到一个组:

这将会把groupAnimation添加到TileView的实体中。注意动画组中可以是一个或者三个动画在一个组,这取决于shouldEnableRipple的值。

现在已经完成了每一个让TileView动起来的函数,接下来在TileGridView中调用它们。转到TileGridView.swift并在startAnimatingWithBeginTime()函数中增加如下代码:

构建并运行

Grid-1

Hmm……这确实看起来好多了,但是AnimatedULogoView的径向膨胀应该对所有方格中的TileView产生震荡波的动画效果。那也就意味着,需要根据外围视图与大纲(基准)视图中心之间的距离设置一个延时补偿乘于适当常数的参量。

在startAnimatingWithBeginTime(:)函数底部,增加如下代码:


上述代码仅仅是获取了从centerTileView中心到视图中心的相对距离。

转到startAnimatingWithBeginTime(_:)并用下列代码进行替换:

 

使用distanceFromCenterViewWithView(_:)函数确定动画所需要的延时。

构建并运行

Grid-2

 

好多了!动画开始看起来得体了,但还是有些缺陷。拼贴块(TileView)应该随着震荡波的方向和起伏符合物理运动。

最好的方法是拿出高中时学的数学(不要畏缩——它将会超越你对它的认识)并根据拼贴块到中心的距离确定拼贴块的移动向量。

 

返回startAnimatingWithBeginTime(_:),对代码进行如下修改:


这段代码计算出了拼贴块(TileView)应该移动的向量并将它作为到了波纹运动补偿值(设为rippleOffset的值)。

构建并运行

Grid-3

 

太酷了!现在来个锦上添花:实现放大效果,但这个动画需要正好在遮罩边界改变之前出现。

在startAnimatingWithBeginTime(_:)函数顶部增加如下代码:


再次构建和运行

FuberFinal

 

漂亮!现在你已经制作了产品级质量的动画,很多Fuber用户将会在Twitter上为此点赞的。

提示:试一试改变 kRippleMagnitudeMultiplier和 kRippleDelayMultiplier的值,会有很有趣的结果的。

最后,转到RootContainerViewController.swift.。在viewDidLoad()函数中最后一行,把showSplashViewControllerNoPing()改成 showSplashViewController()。

最后一次构建和运行程序,欣赏一下自己的工作成果吧。

Fuber Animation

给自己一点赞美……真是一个酷毙了的启动动画!

从这出发可以去干什么?

你可以在这下载Fuber最终完整的工程文件。

如果你想学习更多与动画相关的知识,可以浏览iOS系列动画教程

1 2 收藏 评论

关于作者:飓风飞扬

简介还没来得及写 :) 个人主页 · 我的文章 · 10

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部