android uber启动动画,模仿Uber的启动画面(上)

启动画面(Splash Screen)——不但给开发者们提供了一个尽情发挥、创建有趣动画的机会,也填补了App启动时从终端慢吞吞地下载数据的时间。启动画面(动态的)对于App至关重要:它可以让用户不失兴趣地耐心等待应用完成加载。

尽管现在的启动画面多种多样,但很少有像Uber这般精美的。2016年第一季度,Uber的CEO发表了关于重塑品牌的策略,其中之一就是现在这个超酷的启动画面。

Uber-feature.png

这篇教程的目的是尽可能真实地再现Uber的动画。我们会大量地使用到CALayers、CAAnimations,以及它们的子类。我不会从头介绍这些类的基本概念,而是把重点放在如何应用这些类,创建高质量的动画。如果你想要了解动画背后的基本原理,可以参考Marin Todorove的iOS动画中级教程。

入门

由于有非常多的动画要实现,我们不妨在这个初始项目的基础上进行修改。初始项目里已经为你创建好了所有需要的CALayer,我们给它们添加动画即可。译者注:为了保持教程简洁,删除了原文里一些与教学无关的文字。如有兴趣可通过文章最后的链接阅读原文相关内容。

先来看一眼最终效果:

eac90fd0c1780beb6e7aacf38fb6f6aa.gif

打开初始项目看看里面的文件。

从控制器的角度分析,项目中的SplashViewController通过它的父视图控制器RootContainerViewController生成。SplashViewController会不停循环播放启动动画,直到App完全加载完成,即与终端API握手成功并获取了必要的数据。值得一提的是,在这个示例项目里,启动动画抽象成了一个单独的模块(译者注:可以直接集成到其他项目里)。

RootContainerViewController里有两个方法:showSplashViewController()和showSplashViewControllerNoPing()。我们主要使用第二个方法,它只会不停循环播放动画(不会进入主界面),便于我们把精力集中在SplashViewController的子视图上。当然,最后我们还是会切换回第一个方法,模拟API延迟并过渡到主界面。

启动画面的视图和层

SplashViewController包含了两个子视图。其一是“波纹格子”背景,我们把它叫做TileGridView,    由一系列TileView组成。另一个是带有动画效果的“U”字Logo,我们把它叫做AnimatedULogoView。

264ee84fd2c9d442f09ae7e9cf6e32ea.png

AnimatedULogoView里有4个CAShapeLayer:circleLayer:“U”型Logo的圆形白色背景

lineLayer:circleLayer中心到边界的一条直线

squareLayer:circleLayer中心位置的正方形

maskLayer:遮罩层,当它的边界随着动画改变的时会遮盖其他层

这些CAShapeLayer组合在一起,构成了Fuber标志性的“U”。

既然已经知道这些层的组合方式,那我们可以开始创建动画,让AnimatedULogoView动起来了。

eb9da10b1bf3827cdd19dd3c02a74ab2.gif

圆形的动画

制作动画的时候,最好过滤掉其他视觉“噪音”,只关注当前实现的动画。打开AnimatedULogoView.swift,在init(frame:)方法里,把除了cricleLayer的其他层全都注释掉,实现完动画后我们会重新添加回来。注释后的代码应该像下面一样:override init(frame: CGRect) {  super.init(frame: frame)

circleLayer = generateCircleLayer()

lineLayer = generateLineLayer()

squareLayer = generateSquareLayer()

maskLayer = generateMaskLayer()  //  layer.mask = maskLayer

layer.addSublayer(circleLayer)  //  layer.addSublayer(lineLayer)

//  layer.addSublayer(squareLayer)}

定位到generateCricleLayer()方法,试着理解一下这里的圆是怎么绘制的。其实它只不过是用UIBezierPath绘制的一个CAShapeLayer。注意这一行:layer.path = UIBezierPath(arcCenter: CGPointZero, radius: radius/2, startAngle: -CGFloat(M_PI_2), endAngle: CGFloat(3*M_PI_2), clockwise: true).CGPath

默认情况下,如果你把startAngle设置为0,圆弧会从右侧开始绘制(3点钟方向)。如果设置为-M_PI_2,也就是-90°的话则会从上方开始绘制,endAngle最终是270°,或者说3*M_PI_2,同样也是圆的正上方。另外要注意的是,在这里我们把弧线的宽度lineWidth设置为圆的半径radius,因为我们想让它动起来(画圆的过程)。

circleLayer的动画是由三个CAAnimation组成的:一个描绘笔端动画的CAKeyframeAnimation,一个进行图形变换的CABasicAnimation,以及一个CAAnimationGroup把它们合成在一起。

定位到animateCricleLayer(),添加下面代码:// 笔画变化的动画let strokeEndAnimation = CAKeyframeAnimation(keyPath: "strokeEnd")

strokeEndAnimation.timingFunction = strokeEndTimingFunction

strokeEndAnimation.duration = kAnimationDuration - kAnimationDurationDelay

strokeEndAnimation.values = [0.0, 1.0]

strokeEndAnimation.keyTimes = [0.0, 1.0]

通过把动画的values设置为0.0和1.0,我们告诉Core Animation,从startAngle开始,到endAngle结束,创建像时钟一样的动画。随社storkeEnd的值变大,沿着周长的弧线长度也逐渐增加,整个圆逐渐被填满。对于这个例子,如果你把values改为[0.0, 0.5],那么整个动画只会填满半个圆。

接着添加形变动画:let transformAnimation = CABasicAnimation(keyPath: "transform")

transformAnimation.timingFunction = strokeEndTimingFunction

transformAnimation.duration = kAnimationDuration - kAnimationDurationDelay// 旋转放大的动画// 起始时:逆时针旋转45°,x、y为正常大小的0.25倍var startingTransform = CATransform3DMakeRotation(-CGFloat(M_PI_4), 0, 0, 1)

startingTransform = CATransform3DScale(startingTransform, 0.25, 0.25, 1)

transformAnimation.fromValue = NSValue(CATransform3D: startingTransform)

transformAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity)

这个动画既包括了图形的缩放也包括了沿z轴的旋转。其结果是circleLayer在顺时针旋转45°的同时逐渐放大。这里旋转的参数设置非常重要,因为和其它层的动画组合的时候,它需要和lineLayer的位置及速度相匹配。

最后,在方法的末尾添加一个CAAnimationGroup,它负责把前面两个动画合成在一起,这样你只需给cricleLayer添加一个动画即可。// 把两个动画合成let groupAnimation = CAAnimationGroup()

groupAnimation.animations = [strokeEndAnimation, transformAnimation]

groupAnimation.repeatCount = Float.infinity // 无限重复动画groupAnimation.duration = kAnimationDuration

groupAnimation.beginTime = beginTime

groupAnimation.timeOffset = startTimeOffset

circleLayer.addAnimation(groupAnimation, forKey: "looping")

CAAnimationGroup设定了两个重要的属性:beginTime和timeOffset,如果你对它们不熟悉的话可以参考这篇文章,里面有这两个属性的描述以及用法。

这里的groupAnimation的beginTime属性是根据父视图的时间设定的。

timeOffset在这里也需要设定,因为这个动画在第一次运行的时候,实际上是从中途开始的。当我们完成更多动画时,你可以回到这里,尝试改变startTimeOffset的值并观察效果的差别。

把groupAnimation添加给circleLayer,编译运行一下看看目前的效果:

97c1b84b5a25802ff63e9ddb4e018ed9.gif提示:试着删除strokeEndAnimation或者transformAnimation,看看单独的每一个动画是什么样的。在这篇教程里,你可以尝试不同动画的效果。你可能会惊奇地发现,不同是动画组合竟能创建出如此意想不到的独特视觉效果。

直线的动画

现在我们已经完成circleLayer的动画了,该开始说说lineLayer了。还是在AnimatedULogoView.swift里,定位到startAnimating()把除了animateLineLayer()以外的方法全部注释掉。注释完的代码应该如下面所示:public func startAnimating() {

beginTime = CACurrentMediaTime()

layer.anchorPoint = CGPointZero

//  animateMaskLayer()

//  animateCircleLayer()

animateLineLayer()  //  animateSquareLayer()}

此外还需要调整一下init(frame:),只显示circleLayer和lineLayer:override init(frame: CGRect) {  super.init(frame: frame)

circleLayer = generateCircleLayer()

lineLayer = generateLineLayer()

squareLayer = generateSquareLayer()

maskLayer = generateMaskLayer()  //  layer.mask = maskLayer

layer.addSublayer(circleLayer)

layer.addSublayer(lineLayer)  //  layer.addSublayer(squareLayer)}

注释完毕,定位到animateLineLayer()方法,实现下一组动画效果:// 线段宽度动画let lineWidthAnimation = CAKeyframeAnimation(keyPath: "lineWidth")

lineWidthAnimation.values = [0.0, 5.0, 0.0]

lineWidthAnimation.timingFunctions = [strokeEndTimingFunction, circleLayerTimingFunction]

lineWidthAnimation.duration = kAnimationDuration

lineWidthAnimation.keyTimes = [0.0, 1.0 - kAnimationDurationDelay/kAnimationDuration, 1.0]

这个动画会先增加lineLayer的宽度,随后变回来。

添加下面代码实现下一个动画:// 变形let transformAnimation = CAKeyframeAnimation(keyPath: "transform")

transformAnimation.timingFunctions = [strokeEndTimingFunction, circleLayerTimingFunction]

transformAnimation.duration = kAnimationDuration

transformAnimation.keyTimes = [0.0, 1.0 - kAnimationDurationDelay/kAnimationDuration, 1.0]// 和之前一样的旋转放大动画var transform = CATransform3DMakeRotation(-CGFloat(M_PI_4), 0.0, 0.0, 1.0)

transform = CATransform3DScale(transform, 0.25, 0.25, 1.0)// 先放大再缩小transformAnimation.values = [NSValue(CATransform3D: transform),                             NSValue(CATransform3D: CATransform3DIdentity),                             NSValue(CATransform3D: CATransform3DMakeScale(0.15, 0.15, 1.0))]

和circleLayer的动画非常相似,我们在这里也定义了一个沿z轴顺时针旋转的动画。在这里,我们同样对线条定义了一个缩放动画:从原始大小的25%开始,先变为原始大小,紧接着变为原始大小的15%。

用CAAnimationGroup把它们合成到一起,添加到lineLayer里:// 合成动画let groupAnimation = CAAnimationGroup()

groupAnimation.repeatCount = Float.infinity

groupAnimation.removedOnCompletion = falsegroupAnimation.duration = kAnimationDuration

groupAnimation.beginTime = beginTime

groupAnimation.animations = [lineWidthAnimation, transformAnimation]

groupAnimation.timeOffset = startTimeOffset

lineLayer.addAnimation(groupAnimation, forKey: "looping")

编译运行,观察一下效果。

97469f5d036f3ba06c65f0f899b5b6ae.gif

注意,在这里我们把线条的初始位置设置为了-M_PI_4,同时把keyTimes设置为了[0.0, 1.0 - kAnimationDurationDelay/kAnimationDuration, 1.0]。数组的第一个和最后一个元素显而易见:0.0代表起始,1.0代表终止。为了得到中间时间点,我们需要计算出圆形动画完成、后半部分动画开始(缩小的动画)的时间。用kAnimationDurationDelay除以kAnimationDuration可以得到我们需要的结果。但因为它是个延迟动画,所以我们应该用1.0减去它,我们需要从末尾往前倒,减去延迟时间。

现在我们已经完成了circleLayer和lineLayer的动画了,接下来该处理中间的方形了。

方形的动画

现在你应该已经轻车熟路了。定位到startAnimation()方法,注释掉animateSquareLayer()以外的方法。并把init(frame:)方法修改成下面这样:override init(frame: CGRect) {  super.init(frame: frame)

circleLayer = generateCircleLayer()

lineLayer = generateLineLayer()

squareLayer = generateSquareLayer()

maskLayer = generateMaskLayer()  //  layer.mask = maskLayer

layer.addSublayer(circleLayer)  //  layer.addSublayer(lineLayer)

layer.addSublayer(squareLayer)

}

修改完前往animateSquareLayer(),开始解决下一个动画:// 边框let b1 = NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: 2.0/3.0 * squareLayerLength, height: 2.0/3.0  * squareLayerLength))let b2 = NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: squareLayerLength, height: squareLayerLength))let b3 = NSValue(CGRect: CGRectZero)// 边框从原始长度的2/3开始放大,到原始大小后再逐渐缩小到0let boundsAnimation = CAKeyframeAnimation(keyPath: "bounds")

boundsAnimation.values = [b1, b2, b3]

boundsAnimation.timingFunctions = [fadeInSquareTimingFunction, squareLayerTimingFunction]

boundsAnimation.duration = kAnimationDuration

boundsAnimation.keyTimes = [0, 1.0-kAnimationDurationDelay/kAnimationDuration, 1.0]

上面的动画改变了CALayer的边框。我们创建了一个关键帧动画,从边长的2/3开始,放大到完整尺寸,再缩小到0。

接下来是背景颜色的动画:// 背景颜色的变化let backgroundColorAnimation = CABasicAnimation(keyPath: "backgroundColor")

backgroundColorAnimation.fromValue = UIColor.whiteColor().CGColorbackgroundColorAnimation.toValue = UIColor.fuberBlue().CGColorbackgroundColorAnimation.timingFunction = squareLayerTimingFunction

backgroundColorAnimation.fillMode = kCAFillModeBoth

backgroundColorAnimation.beginTime = kAnimationDurationDelay * 2.0 / kAnimationDuration

backgroundColorAnimation.duration = kAnimationDuration / (kAnimationDuration - kAnimationDurationDelay)

注意这里的fillMode属性。由于beginTime不是零,动画会把开始和结束时的CGColor包含进去。因此,当我们把动画添加到父CAAnimationGroup里的时候不会闪现不同颜色。

说到CAAnimationGroup,该实现它了:// 合成动画let groupAnimation = CAAnimationGroup()

groupAnimation.animations = [boundsAnimation, backgroundColorAnimation]

groupAnimation.repeatCount = Float.infinity

groupAnimation.duration = kAnimationDuration

groupAnimation.removedOnCompletion = falsegroupAnimation.beginTime = beginTime

groupAnimation.timeOffset = startTimeOffset

squareLayer.addAnimation(groupAnimation, forKey: "looping")

编译运行,检查一下我们的进度,嗯看来方形动画已经顺利完成了。

0eb38645aa0535cc195ba63756a54a37.gif

是时候把之前的动画合并到一起看看效果了!提示:在iOS模拟器里的动画可能会有些卡顿,因为我们需要在Mac上模拟平时由iOS的GPU完成的工作。如果你的电脑不能流畅运行动画,试着缩小模拟器的屏幕大小,或者在真机上测试。

遮罩层

首先取消init(frame:)和startAnimating()里所有注释。

把所有动画组合到一起,我们重新编译运行一下Fuber。

5c79919f5c5609719bba47d8b6b2915c.gif

看起来好像还是差点意思?cricleLayer的缩小消失太过突然。幸运的是,遮罩层动画可以修正这个问题,让它平滑地缩小。

定位到animateMaskLayer()添加下面的代码:// 边框缩小let boundsAnimation = CABasicAnimation(keyPath: "bounds")

boundsAnimation.fromValue = NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: radius * 2.0, height: radius * 2))

boundsAnimation.toValue = NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: 2.0/3.0 * squareLayerLength, height: 2.0/3.0 * squareLayerLength))

boundsAnimation.duration = kAnimationDurationDelay

boundsAnimation.beginTime = kAnimationDuration - kAnimationDurationDelay

boundsAnimation.timingFunction = circleLayerTimingFunction

上面的代码用于设定遮罩层边界动画。别忘了,当遮罩层的边界改变时,整个AnimatedULogoView都会被遮挡,因为它作用于所有子层。

现在我们来实现圆角动画,保持遮罩是圆形的:// 边角弧度let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")

cornerRadiusAnimation.beginTime = kAnimationDuration - kAnimationDurationDelay

cornerRadiusAnimation.duration = kAnimationDurationDelay

cornerRadiusAnimation.fromValue = radius

cornerRadiusAnimation.toValue = 2cornerRadiusAnimation.timingFunction = circleLayerTimingFunction

把这两个动画合成为一个CAAnimationGroup,这个层就完成了:// 合成动画let groupAnimation = CAAnimationGroup()

groupAnimation.removedOnCompletion = falsegroupAnimation.fillMode = kCAFillModeBoth

groupAnimation.beginTime = beginTime

groupAnimation.repeatCount = Float.infinity

groupAnimation.duration = kAnimationDuration

groupAnimation.animations = [boundsAnimation, cornerRadiusAnimation]

groupAnimation.timeOffset = startTimeOffset

maskLayer.addAnimation(groupAnimation, forKey: "looping")

编译运行。

a517c21442995ddefd23ac3082c9621d.gif

看起来不错!

教程的上半部分至此结束,关于背景网格的水波效果会在下一篇教程中介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531589.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧屏用鸿蒙的生态,紧随鸿蒙OS手机版 ,智慧屏为什么对鸿蒙生态这么重要?...

原标题:紧随鸿蒙OS手机版 ,智慧屏为什么对鸿蒙生态这么重要?12 月 21 日,华为正式发布了两款智慧屏新品,智慧屏 S 系列和车载智慧屏,前者是智慧屏的新系列,后者则是新开辟的车机产品线。没有意外…

MySQL不能插入中文字符及中文字符乱码问题

MySQL的默认编码是Latin1,不支持中文,要支持中午需要把数据库的默认编码修改为gbk或者utf8。在安装后MySQL之后,它的配置文件不是很给力,不知道你们的是不是,反正我的是! 开始插入中文字符的时候出现如下错…

codeforces C. Bits(数学题+或运算)

题意:给定一个区间,求区间中的一个数,这个数表示成二进制的时候,数字1的个数最多! 如果有多个这样的数字,输出最小的那个! 思路:对左区间的这个数lx的二进制 从右往左将0变成1&#…

r语言 发送邮件html,r语言读取数据的方法

R 对于基于 SQL 语言的关系型数据库有良好的支持,这些数据库既有商业数据库 Oracle、Microsoft SQL Server、IBM DB2 等,也包含在 GNUGeneral Public License (GPL) 下发布的 MySQL 等开源数据库。RMySQL 包中提供了到 MySQL 数据库的接口;RO…

eclipse开发web应用程序步骤(图解)

*运行环境(也就是服务器的选择) 环境搭建好之后开始编写web程序!然后右键->Run as -> Run on Server! 转载于:https://www.cnblogs.com/hujunzheng/p/4083560.html

android 测光模式,Android Camera1中的对焦与测光

Android Camera1开发系列连载:Android Camera1详解Android Camera1显示预览的四种方式对焦模式在使用特定的对焦模式的时候,必须确保相机支持该模式,相机支持的对焦模式可以通过Parameters#getFocusMode接口来获取:常用的对焦模式…

html5做一个展示页面,基于HTML5的WebGL实现json和echarts图表展现在同一个界面

突然有个想法,如果能把一些用到不同的知识点放到同一个界面上,并且放到一个盒子里,这样我如果要看什么东西就可以很直接显示出来,而且这个盒子一定要能打开。我用HT实现了我的想法,代码一百多行,这么少的代…

Netbeans 中创建数据连接池和数据源步骤(及解决无法ping通问题)

1.启动glassfish服务器, 在浏览器的地址栏中输入 http://localhost:4848 2.首先建立JDBC Connection Pools; 3.new 一个Connectio Pools 4.对General Settings属性填写; 5.填写Datasource Classname:com.mysql.jdbc.jdbc2.optiona…

Netbeans不能正常启动glassfish或者部署失败不能运行的问题

错误信息:D:\临时文件\netbeans\WebTest\build\web中部署GlassFish Server 4, deploy, Connection refused: connect, falseD:\临时文件\netbeans\WebTest\nbproject\build-impl.xml:1048: 尚未部署该模块。有关详细信息, 请查看服务器日志。构建失败 (总时间: 7 秒…

计算机的好处英语,电脑的好处英语演讲稿

电脑的好处英语演讲稿The computer plays the vital role in ours life, the computer may help us to handle very many matters: The data computation, the study entertainment, the office automation, the control production, draws money automatically, long-distance…

计算机重应用,装了一大堆应用,iPhone6会变重吗?

你有没有想过,iPhone买来以后装入数十甚至上百个应用,手机是否在重量上发生了变化。广泛接受马克思主义教育的人类表示:别闹了,数据和信息属于意识,怎么能有重量(或者应该叫质量)。更何况这个所谓的“信息爆炸”时代每…

电子商务专业需要考计算机证吗,电子商务必考的证有哪些

2020-03-14 16:31:11文/钟诗贺电子商务是当今非常热门的学科,必考的专业证书主要有电子商务员、电子商务师等等。电子商务专业介绍电子商务专业是融计算机科学、市场营销学、管理学、经济学、法学和现代物流于一体的新型交叉学科。该专业培养掌握计算机信息技术、市…

陷阱计算机音乐谱大全,陷阱 原版C调-王北车-和弦谱-《弹吧》官网tan8.com-和弦谱大全,学吉他,秀吉他...

E此浏览器不支持画布前奏扫弦 C Fm C Fm C Am F G7主C Em一封信两年都没动笔C Am三个字过了几个四季F G你是有多想逃避Em Am来不及问问你Dm G我已经错过相爱的日期C Em那天你消失在人海里C Am你的背影沉默得让人恐惧F G Em Am你说的那些问题 我回答得很坚定Dm G偏偏那个时候我最…

江苏省公务员计算机类130分,130分,在江苏省考中是什么水平?

原标题:130分,在江苏省考中是什么水平?关键词回复获取更多详情更多公考常识,后台回复【常识】更多公考素材,后台回复【素材】【130】在江苏算是公考进面的一道普遍门槛。对于2020江苏省考的考生,130分应定为…

软件测试项目时间一般多少钱,项目的时间进度该如何估算?

后来,开发一周左右,发现原来使用的模块,要用新的接口A,但是接口A还不能满足我现有的需求,必须需要程序员A重新包装给我,这样再等了一下午,第二天,问题不断,不停的跟程序员…

计算机寄存器端口,CPU和外设之间的数据传送方式有哪几种

数据传输(data transmission),指的是依照适当的规程,经过一条或多条链路,在数据源和数据宿之间传送数据的过程。也表示借助信道上的信号将数据从一处送往另一处的操作。CPU与外设之间的数据传输有以下三种方式:程序方式、中断方式…

茌平计算机中考成绩查询,中考成绩查询系统入口2021

湘潭市的中考成绩查询,需要在通道开通之后进行,那么查询通道在哪里呢?想必大家都很想知道。下面出国留学网小编为大家带来湘潭中考成绩查询系统入口2021,仅供参考,欢迎阅读。拓展阅读:中考后有哪些选择出路…

win10证书服务器不可用,win10系统提示“安全证书的吊销信息不可用”的修复方法...

在windows10系统下一位用户遇到了奇怪的问题,该用户表示浏览网页的时候经常会弹出安全警报:该镇点安全证书的吊销信息不可用。是否继续? 的报错弹窗,这是怎么回事呢?其实,该问题是由于ie浏览器中的设置出现…

暴走大侠显示进入服务器失败,暴走大侠:常见问题详解,再遇见这样的问题也不再迷糊...

相信玩了这么久的英雄好汉们,对这个游戏的理解已经有了一定的了解。同时也应该有很多疑问,几天就给大家详细的讲解下最近几天大家关心最多的问题。关于门派选择问题,主动技能怎学?答:门派问题的话,这个最好…

mysql数据库移植

在mysql数据库移植的时候,把自己电脑上mysql中data目录的一些重要文件复制到其他电脑上,先备份一下其他电脑上的mysql的data目录,然后替换! 例如我的mysql默认的数据库文件位置: C:\ProgramData\MySQL\MySQL Server 5.…