如何制作一个类似Uber的溅落式启动屏

478 查看

本文翻译自 How To Create an Uber Splash ScreenDerek Selander 发表于Raywenderlich

受限于译者英语水平及翻译经验,译文容难免有词不达意,甚至翻译错误的地方,还望不吝赐教予以指正

一个好的溅落式启动页(别被毫无动画效果的静态启动页迷惑),使开发人员有机会在展示动画期间,从后端获取必要的数据。同时它在应用启动期间让用户始终保持高昂兴趣方面也发挥了重要作用。

虽然溅落式启动页已广泛存在,但是你很难找到一个如Uber这般出色的。在2016年的首季,Uber释出一个由CEO领导的品牌重塑战略,品牌重塑的成果之一,便是一个非常炫酷的溅落式启动页。

本文以仿制Uber启动动画为目标。其中运用了大量的CAlayerCAAnimation类,及其相应子类。相对于概念介绍,本文更着重于如何运用这些类去实现一个产品级的动画效果。如需了解动画背后的相关知识,请访问 Marin Todorov 的系列视频教程: Intermediate iOS Animation

开始

鉴于本文涉及的动画众多,这里提供一个已为后续动画创建好所有CALayer的起始工程

起始工程是一个叫做Fuber的应用,Fuber提供(Segway)驾乘共享服务,乘客通过向Segway驾驶员发出请求,来邀请其搭载自己抵达城市的任何地方。Fuber发展迅速,已在60多个国家为用户提供服务,但也面临众多国家的反对和工会要求其必须与司机签订合同的问题。:](原作者卖萌了)

1168747470733a2f2f63646e312e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30352f66756265725f6c6f676f2d343830783136312e706e67

最终,我们会创建一个如下的非常炫酷的溅落式启动页:

打开并运行起始工程,简单浏览一下工程结构。

首先从视图控制器开始,应用通过负责子视图(切入)切出任务的RootContainerViewController加载SplashViewController。父视图控制器从启动页开始运行,直至应用的所有准备工作全部完成。这期间应用会连接到后端,获取后续所需数据。需要指出的是,在这个简单的项目中启动页被设计成了一个独立的模块。

RootContainerViewController中已经实现好了两个方法:showSplashViewController()showSplashViewControllerNoPing()。 由于教程中大部分时间,都在调用showSplashViewControllerNoPing()方法(调试启动动画),所以我们先将精力放在SplashViewController的子视图动画创建上,然后在通过showSplashViewController()模拟一个访问API的延迟效果,并随即跳转到主视图控制器。

溅落式启动页视图及其图层结构

SplashViewController的视图(view)包含两个子视图(subview)。 第一个子视图是用于构成波纹网格背景的TileGridview,它包含了一系列按网格排列的TileView实例。另一个子视图名为AnimatedULogoView,它构成了 U 字型的动画图标。

1268747470733a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30352f46756265722d566965772d4869657261726368792d312e706e67

AnimatedULogoView包含4个CAShapeLayer:

  • circleLayer 用于实现字母 U 的白色背景
  • lineLayer 用于实现从circleLayer的中心到边缘的一条线段
  • squareLayer 用于实现位于circleLayer中心位置的方块
  • maskLayer 用作视图遮罩,通过改变其bounds的动画效果,来将其它所有图层的动画效果整齐划一地混合起来。

通过组合这几个CAShaperLayer动画,共同实现了Fuber中字母 U 的动画效果。

了解了图层的构成之后,接下来我们就来添加一些动画让AnimatedULogoView动起来吧。

让圆形动起来

创建复杂动画的关键,在于排除视觉干扰专注于我们正在实现的部分。 打开AnimatedULogoView.swift文件。找到init(frame:)方法,注释掉除circleLayer外其它向视图中添加子图层(sublayer)的方法,完成动画后会再将其全部添加回来。注释完成后的代码如下:

找到generateCircleLayer()方法,了解下圆形是如何被创建的。其实只是简单地通过 UIBezierPath 创建了一个 CAShapeLayer (图层)。 注意看这行代码:

startAngle 传入 0 或使用默认值, 弧线会从右侧(3点钟位置)开始。传入 -M_PI_2 即 -90度, 则会从顶部开始,如果 endAngle 恰好是270度即 3 * M_PI_2,弧线则再次回到顶点(形成一个圆形)。注意为了绘制的动画效果,我们使用圆形的半径作为lineWidth

circleLayer的动画需要三个CAAnimation子类来实现:一个作用于stokeEndCAKeyframeAnimation动画,一个作用于transformCABasicAnimation动画,和一个负责将两部分动画组合起来的CAAnimationGroup。这将一次性同时创建所有动画。

在事先写好的animateCircleLayer()方法中添加如下代码:

通过向动画的Values属性提供的 0.0 和 1.0,我们便透过Core Animation框架生成了一个从 startAngleendAngle 沿顺时针旋转的动画。随着 strokeEnd 属性值的增加,弧线沿着圆周慢慢伸展,圆形也渐渐被”填满”。在这个例子中,如果我们将values属性的值设为[0.0, 0.5],则仅会画半个圆,这是因为 StrokeEnd 在动画结束时刚达好到圆周的一半。

译者注:“圆形也渐渐被‘填满’”一句的填满是引起来的,并不是真的被填满,而是描边的 lineWidth 与圆形半径相同,从而产生了填满的视觉效果。可参考generateCircleLayer()方法中layer.fillColor = UIColor.clear.cgColor这段代码,事实上填充色被设置为透明,

现在添加形变(transform)动画:

该动画同时实现了放大和沿 Z 轴旋转的两个形变。这使得圆形在沿顺时针旋转45度的同时逐渐变大。这里的旋转很重要,因为圆形的旋转要与lineLayer和其它图层一块动起来时的位置和速度保持一致。

最后在animateCircleLayer()方法的最下面添加一个CAAnimationGroup。这个动画组将包含之前的两个动画,这样我们仅向circleLayer图层添加一次动画即可。