iOSAPP启动效果复杂动画之抽丝剥茧
一、前言
随着开发者的增多和时间的累积,AppStore已经有非常多的应用了,每年都有很多新的APP产生。但是我们手机上留存的应用有限,所以如何吸引用户,成为产品设计的一项重要内容。其中炫酷的动画效果是重要内容之一,我们会发现很多好的应用上面都有许多很炫的效果。可能一提到炫酷的动画,很多人都很头疼,因为动画并不是那么好做,实现一个好的动画需要时间、耐心和好的思路。下面我们就以一个有趣的动画(如下图)为例,抽丝剥茧,看看到底是怎么实现的!
二、分析
上面图中的动画第一眼看起来的确是有点复杂,但是我们来一步步分析,就会发现其实并不是那么难。仔细看一下就会发现,大致步骤如下:
1、先出来一个圆
2、圆形在水平和竖直方向上被挤压,呈椭圆形状的一个过程,最后恢复成圆形
3、圆形的左下角、右下角和顶部分别按顺序凸出一小部分
4、圆和凸出部分形成的图形旋转一圈后变成三角形
5、三角形的左边先后出来两条宽线,将三角形围在一个矩形中
6、矩形由底部向上被波浪状填满
7、被填满的矩形放大至全屏,弹出Welcome
动画大致就分为上面几个步骤,拆分后我们一步步来实现其中的效果(下面所示步骤中以Swift代码为例,demo中分别有Objective-C和Swift的实现)。
三、实现圆形以及椭圆的渐变
首先,我们创建了一个新工程后,然后新建了一个名AnimationView的类继承UIView,这个是用来显示动画效果的一个view。然后先添加CircleLayer(圆形layer),随后实现由小变大的效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class AnimationView: UIView { let circleLayer = CircleLayer() override init(frame: CGRect) { super .init(frame: frame) backgroundColor = UIColor.clearColor() addCircleLayer() } required init?(coder aDecoder: NSCoder) { super .init(coder: aDecoder) } /** add circle layer */ func addCircleLayer() { self.layer.addSublayer(circleLayer) circleLayer.expand() } } |
其中expand()这个方法如下
1
2
3
4
5
6
7
8
9
10
11
12
|
/** expand animation function */ func expand() { let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path" ) expandAnimation.fromValue = circleSmallPath.CGPath expandAnimation.toValue = circleBigPath.CGPath expandAnimation.duration = KAnimationDuration expandAnimation.fillMode = kCAFillModeForwards expandAnimation.removedOnCompletion = false self.addAnimation(expandAnimation, forKey: nil) } |
运行效果如下
第一步做好了,接下来就是呈椭圆形状的变化了,仔细分析就比如一个弹性小球,竖直方向捏一下,水平方向捏一下这样的效果。这其实就是一个组合动画,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/** wobbl group animation */ func wobbleAnimate() { // 1、animation begin from bigPath to verticalPath let animation1: CABasicAnimation = CABasicAnimation(keyPath: "path" ) animation1.fromValue = circleBigPath.CGPath animation1.toValue = circleVerticalSquishPath.CGPath animation1.beginTime = KAnimationBeginTime animation1.duration = KAnimationDuration // 2、animation vertical to horizontal let animation2: CABasicAnimation = CABasicAnimation(keyPath: "path" ) animation2.fromValue = circleVerticalSquishPath.CGPath animation2.toValue = circleHorizontalSquishPath.CGPath animation2.beginTime = animation1.beginTime + animation1.duration animation2.duration = KAnimationDuration // 3、animation horizontal to vertical let animation3: CABasicAnimation = CABasicAnimation(keyPath: "path" ) animation3.fromValue = circleHorizontalSquishPath.CGPath animation3.toValue = circleVerticalSquishPath.CGPath animation3.beginTime = animation2.beginTime + animation2.duration animation3.duration = KAnimationDuration // 4、animation vertical to bigPath let animation4: CABasicAnimation = CABasicAnimation(keyPath: "path" ) animation4.fromValue = circleVerticalSquishPath.CGPath animation4.toValue = circleBigPath.CGPath animation4.beginTime = animation3.beginTime + animation3.duration animation4.duration = KAnimationDuration // 5、group animation let animationGroup: CAAnimationGroup = CAAnimationGroup() animationGroup.animations = [animation1, animation2, animation3, animation4] animationGroup.duration = 4 * KAnimationDuration animationGroup.repeatCount = 2 addAnimation(animationGroup, forKey: nil) } |
上面代码中实现了从 圆 → 椭圆(x方向长轴)→ 椭圆(y方向长轴)→ 圆这一系列的变化,最后组合成一个动画。这一步实现后效果如下
四、实现圆形边缘的凸出部分
关于这个凸出部分,乍一看可能感觉会比较难实现,看起来挺复杂的。其实实现的原理很简单,仔细分析我们会发现这三个凸出部分连起来刚好是一个三角形,那么第一步我们就在之前的基础上先加一个三角形的layer,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import UIKit class TriangleLayer: CAShapeLayer { let paddingSpace: CGFloat = 30.0 override init() { super .init() fillColor = UIColor.colorWithHexString( "#009ad6" ).CGColor strokeColor = UIColor.colorWithHexString( "#009ad6" ).CGColor lineWidth = 7.0 path = smallTrianglePath.CGPath } required init?(coder aDecoder: NSCoder) { fatalError( "init(coder:) has not been implemented" ) } var smallTrianglePath: UIBezierPath { let smallPath = UIBezierPath() smallPath.moveToPoint(CGPointMake(5.0 + paddingSpace, 95.0)) smallPath.addLineToPoint(CGPointMake(50.0, 12.5 + paddingSpace)) smallPath.addLineToPoint(CGPointMake(95.0 - paddingSpace, 95.0)) smallPath.closePath() return smallPath } } |
然后设置圆角
1
2
|
lineCap = kCALineCapRound lineJoin = kCALineJoinRound |
下面就是来做凸出部分了,原理其实很简单,就是将现在这个三角形保持中心不变,左边向左延伸即可
然后同理,保持中心不变分别按顺序向右和向上拉伸
具体过程是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/** triangle animate function */ func triangleAnimate() { // left let triangleAnimationLeft: CABasicAnimation = CABasicAnimation(keyPath: "path" ) triangleAnimationLeft.fromValue = smallTrianglePath.CGPath triangleAnimationLeft.toValue = leftTrianglePath.CGPath triangleAnimationLeft.beginTime = 0.0 triangleAnimationLeft.duration = 0.3 // right let triangleAnimationRight: CABasicAnimation = CABasicAnimation(keyPath: "path" ) triangleAnimationRight.fromValue = leftTrianglePath.CGPath triangleAnimationRight.toValue = rightTrianglePath.CGPath triangleAnimationRight.beginTime = triangleAnimationLeft.beginTime + triangleAnimationLeft.duration triangleAnimationRight.duration = 0.25 // top let triangleAnimationTop: CABasicAnimation = CABasicAnimation(keyPath: "path" ) triangleAnimationTop.fromValue = rightTrianglePath.CGPath triangleAnimationTop.toValue = topTrianglePath.CGPath triangleAnimationTop.beginTime = triangleAnimationRight.beginTime + triangleAnimationRight.duration triangleAnimationTop.duration = 0.20 // group let triangleAnimationGroup: CAAnimationGroup = CAAnimationGroup() triangleAnimationGroup.animations = [triangleAnimationLeft, triangleAnimationRight, triangleAnimationTop] triangleAnimationGroup.duration = triangleAnimationTop.beginTime + triangleAnimationTop.duration triangleAnimationGroup.fillMode = kCAFillModeForwards triangleAnimationGroup.removedOnCompletion = false addAnimation(triangleAnimationGroup, forKey: nil) } |
我们接下来把三角形的颜色改一下
这里颜色相同了我们就可以看到了这个凸出的这个效果,调到正常速率(为了演示,把动画速率调慢了) ,联合之前所有的动作,到现在为止,效果是这样的
到现在为止,看上去还不错,差不多已经完成一半了,继续下一步!
五、实现旋转和矩形
旋转来说很简单了,大家估计都做过旋转动画,这里就是把前面形成的图形旋转一下(当然要注意设置锚点anchorPoint)
1
2
3
4
5
6
7
8
9
10
11
|
/** self transform z */ func transformRotationZ() { self.layer.anchorPoint = CGPointMake(0.5, 0.65) let rotationAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z" ) rotationAnimation.toValue = CGFloat(M_PI * 2) rotationAnimation.duration = 0.45 rotationAnimation.removedOnCompletion = true layer.addAnimation(rotationAnimation, forKey: nil) } |
旋转之后原图形被切成了一个三角形,思路就是把原来的大圆,按着这个大三角形的内切圆剪切一下即可
1
2
3
4
5
6
7
8
9
10
11
12
|
/** contract animation function */ func contract() { let contractAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path" ) contractAnimation.fromValue = circleBigPath.CGPath contractAnimation.toValue = circleSmallPath.CGPath contractAnimation.duration = KAnimationDuration contractAnimation.fillMode = kCAFillModeForwards contractAnimation.removedOnCompletion = false addAnimation(contractAnimation, forKey: nil) } |
接下来就是画矩形,新建一个RectangleLayer,划线
1
2
3
4
5
6
7
8
9
10
11
12
|
/** line stroke color change with custom color - parameter color: custom color */ func strokeChangeWithColor(color: UIColor) { strokeColor = color.CGColor let strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd" ) strokeAnimation.fromValue = 0.0 strokeAnimation.toValue = 1.0 strokeAnimation.duration = 0.4 addAnimation(strokeAnimation, forKey: nil) } |
最后面就是经典的水波纹动画了,不多说,直接上代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
![WavetAnimation.gif](http: //upload-images.jianshu.io/upload_images/571495-856dc8f307d16f60.gif?imageMogr2/auto-orient/strip) func animate() { /// 1 let waveAnimationPre: CABasicAnimation = CABasicAnimation(keyPath: "path" ) waveAnimationPre.fromValue = wavePathPre.CGPath waveAnimationPre.toValue = wavePathStarting.CGPath waveAnimationPre.beginTime = 0.0 waveAnimationPre.duration = KAnimationDuration /// 2 let waveAnimationLow: CABasicAnimation = CABasicAnimation(keyPath: "path" ) waveAnimationLow.fromValue = wavePathStarting.CGPath waveAnimationLow.toValue = wavePathLow.CGPath waveAnimationLow.beginTime = waveAnimationPre.beginTime + waveAnimationPre.duration waveAnimationLow.duration = KAnimationDuration /// 3 let waveAnimationMid: CABasicAnimation = CABasicAnimation(keyPath: "path" ) waveAnimationMid.fromValue = wavePathLow.CGPath waveAnimationMid.toValue = wavePathMid.CGPath waveAnimationMid.beginTime = waveAnimationLow.beginTime + waveAnimationLow.duration waveAnimationMid.duration = KAnimationDuration /// 4 let waveAnimationHigh: CABasicAnimation = CABasicAnimation(keyPath: "path" ) waveAnimationHigh.fromValue = wavePathMid.CGPath waveAnimationHigh.toValue = wavePathHigh.CGPath waveAnimationHigh.beginTime = waveAnimationMid.beginTime + waveAnimationMid.duration waveAnimationHigh.duration = KAnimationDuration /// 5 let waveAnimationComplete: CABasicAnimation = CABasicAnimation(keyPath: "path" ) waveAnimationComplete.fromValue = wavePathHigh.CGPath waveAnimationComplete.toValue = wavePathComplete.CGPath waveAnimationComplete.beginTime = waveAnimationHigh.beginTime + waveAnimationHigh.duration waveAnimationComplete.duration = KAnimationDuration /// group animation let arcAnimationGroup: CAAnimationGroup = CAAnimationGroup() arcAnimationGroup.animations = [waveAnimationPre, waveAnimationLow, waveAnimationMid, waveAnimationHigh, waveAnimationComplete] arcAnimationGroup.duration = waveAnimationComplete.beginTime + waveAnimationComplete.duration arcAnimationGroup.fillMode = kCAFillModeForwards arcAnimationGroup.removedOnCompletion = false addAnimation(arcAnimationGroup, forKey: nil) } |
找几个点控制水波形状,画出贝塞尔曲线即可,到这里基本就完成了。接下来最后一步,放大,并弹出Welcome
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func expandView() { backgroundColor = UIColor.colorWithHexString( "#40e0b0" ) frame = CGRectMake(frame.origin.x - blueRectangleLayer.lineWidth, frame.origin.y - blueRectangleLayer.lineWidth, frame.size.width + blueRectangleLayer.lineWidth * 2, frame.size.height + blueRectangleLayer.lineWidth * 2) layer.sublayers = nil UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { self.frame = self.parentFrame }, completion: { finished in self.delegate?.completeAnimation() }) } |
放大完以后设置代理,然后在主的vc中添加Welcome这个Label
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// MARK: - // MARK: AnimationViewDelegate func completeAnimation() { // 1 animationView.removeFromSuperview() view.backgroundColor = UIColor.colorWithHexString( "#40e0b0" ) // 2 let label: UILabel = UILabel(frame: view.frame) label.textColor = UIColor.whiteColor() label.font = UIFont(name: "HelveticaNeue-Thin" , size: 50.0) label.textAlignment = NSTextAlignment.Center label.text = "Welcome" label.transform = CGAffineTransformScale(label.transform, 0.25, 0.25) view.addSubview(label) // 3 UIView.animateWithDuration(0.4, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: UIViewAnimationOptions.CurveEaseInOut,animations: ({ label.transform = CGAffineTransformScale(label.transform, 4.0, 4.0) }), completion: { finished in self.addTouchButton() }) } |
到现在为止,动画全部完成
六、最后
两个版本(Objective-C & Swift)源代码:http://download.csdn.net/detail/hbblzjy/9564685
iOSAPP启动效果复杂动画之抽丝剥茧的更多相关文章
- Hover.css:一组超实用的 CSS3 悬停效果和动画
Hover.css 是一套基于 CSS3 的鼠标悬停效果和动画,这些可以非常轻松的被应用到按钮.LOGO 以及图片等元素.所有这些效果都是只需要单一的标签,必要的时候使用 before 和 after ...
- CodePen 作品秀:Canvas 粒子效果文本动画
作品名称——Shape Shifter,基于 Canvas 的粒子图形变换实验.在页面下方的输入框输入文本,上面就会进行变换出对应的粒子效果文本动画. CodePen 作品秀系列向大家展示来自 Cod ...
- 基于canvas实现物理运动效果与动画效果(一)
一.为什么要写这篇文章 某年某月某时某种原因,我在慕课网上看到了一个大神实现了关于小球的抛物线运动的代码,心中很是欣喜,故而写这篇文章来向这位大神致敬,同时也为了弥补自己在运动效果和动画效果制作方面的 ...
- html5--6-55 动画效果-关键帧动画
html5--6-55 动画效果-关键帧动画 实例 @charset="UTF-8"; div{ width: 150px; height: 150px; font-size: 2 ...
- iOS复杂动画之抽丝剥茧(Objective-C & Swift)
一.前言 随着开发者的增多和时间的累积,AppStore已经有非常多的应用了,每年都有很多新的APP产生.但是我们手机上留存的应用有限,所以如何吸引用户,成为产品设计的一项重要内容.其中炫酷的动画效果 ...
- iOS 复杂动画之抽丝剥茧
一.前言 随着开发者的增多和时间的累积,AppStore已经有非常多的应用了,每年都有很多新的APP产生.但是我们手机上留存的应用有限,所以如何吸引用户,成为产品设计的一项重要内容.其中炫酷的动画效果 ...
- 怎样做一个iOS App的启动分层引导动画?
一. 为什么要写这篇文章? 这是一个很古老的话题,从两年前新浪微博开始使用多层动画制作iOS App的启动引导页让人眼前一亮(当然,微博是不是历史第一个这个问题值得商榷)之后,各种类型的引导页层出不穷 ...
- css3 3d效果及动画学习
css参考手册: http://www.phpstudy.net/css3/ http://www.css88.com/book/css/ 呈现3d效果:-webkit-transform-style ...
- Android程序启动加载动画实现
package com.example.bmob_test.ui;//程序启动动画,图片颜色由浅到深,方法一 import com.example.bmob_test.LogActivity; imp ...
随机推荐
- LAB颜色空间各通道的取值范围
简介 LAB颜色空间在计算机视觉中经常被使用,知道L,A,B三个通道的取值范围有一定的意义. OpenCV获取LAB取值范围 下面是一段实验代码,用于获取LAB的取值范围. 基本思路是,排列组合所有R ...
- 关于在arm裸板编程时使用printf问题的解决方法
在ARM裸板驱动编程中,是不允许程序直接调用C库程序的.为什么呢?因为此时kernel还没有被加载,所以在封装在kernel层的C库的API是用不了的,那怎么办? 在开发过程中,printf的功能我不 ...
- Python 描述符 data 和 non-data 两种类型
仅包含__get__的,是non-data descriptor, 如果实例__dict__包含同名变量, 则实例优先; 如果还包含__set__, 则是data descriptor, 优先于实例_ ...
- JAVA通过继承Thread来创建线程
创建一个线程的第二种方法是创建一个新的类,该类继承Thread类,然后创建一个该类的实例. 继承类必须重写run()方法,该方法是新线程的入口点.它也必须调用start()方法才能执行. 实例 // ...
- Redis中的关系查询
本文对Redis如何保存关系型数据,以及如何对其匹配.范围.模糊查询进行举例讲解,其中模糊查询功能基于最新的2.8.9以后版本. 1 关系型数据的存储 以Staff对象为例,在关系型数据库或类似Gri ...
- Dynamics CRM2013 Odata的filter中含有日期字段时遇到的一个奇葩问题
在使用Odata拼写filter时我们一般都用工具,因为手写是件极不靠谱且错误率极高的事,下图是我用query designer拼出来的一个filter,因为时间是参数,所以在拷贝出下面这段filte ...
- 协议系列之UDP协议
上节说的TCP协议虽然提供了可靠的传输,但是也有一个缺点,发送速度慢.那么有没有一种协议能快速发送的呢?这节要讨论的就是UDP协议,它提供了更加快的发送速度,但也牺牲了可靠性,它是一种无连接的传输协议 ...
- Dynamics CRM2015 页面导航栏顶部全局快速查找功能配置
在CRM2015中微软加入了新的快速查找功能,让你的数据查找更加方便,功能栏如下图所示,直接可以框中输入搜索项进行搜索. 但该功能是需要进行些配置,具体的配置在设置-管理-系统设置中,默认的就是红框中 ...
- 指令汇C电子市场开发(一) ActionBar的使用
前话: 在学习开发谷歌电子市场的的时候,我换了一款比较高大上的模拟器--genymotion,首先去genymotion的官网注册下载,然后安装.感觉这款模拟器运行挺快的,哈哈,而且可以直接把应用拖进 ...
- Android 之Toast讲解-android学习之旅(一)
Toast比较常用,用于显示简短的提醒,比如网络连接断开等. Toast的简单编码实例 findViewById(R.id.button1).setOnClickListener(new OnClic ...