一、前言

随着开发者的增多和时间的累积,AppStore已经有非常多的应用了,每年都有很多新的APP产生。但是我们手机上留存的应用有限,所以如何吸引用户,成为产品设计的一项重要内容。其中炫酷的动画效果是重要内容之一,我们会发现很多好的应用上面都有许多很炫的效果。可能一提到炫酷的动画,很多人都很头疼,因为动画并不是那么好做,实现一个好的动画需要时间、耐心和好的思路。下面我们就以一个有趣的动画(如下图)为例,抽丝剥茧,看看到底是怎么实现的!

二、分析

上面图中的动画第一眼看起来的确是有点复杂,但是我们来一步步分析,就会发现其实并不是那么难。仔细看一下就会发现,大致步骤如下:

1、先出来一个圆

2、圆形在水平和竖直方向上被挤压,呈椭圆形状的一个过程,最后恢复成圆形

3、圆形的左下角、右下角和顶部分别按顺序凸出一小部分

4、圆和凸出部分形成的图形旋转一圈后变成三角形

5、三角形的左边先后出来两条宽线,将三角形围在一个矩形中

6、矩形由底部向上被波浪状填满

7、被填满的矩形放大至全屏,弹出Welcome

动画大致就分为上面几个步骤,拆分后我们一步步来实现其中的效果(下面所示步骤中以Swift代码为例,demo中分别有Objective-C和Swift的实现)。

三、实现圆形以及椭圆的渐变

首先,我们创建了一个新工程后,然后新建了一个名AnimationView的类继承UIView,这个是用来显示动画效果的一个view。然后先添加CircleLayer(圆形layer),随后实现由小变大的效果。

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()这个方法如下

/**

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)

}

运行效果如下

第一步做好了,接下来就是呈椭圆形状的变化了,仔细分析就比如一个弹性小球,竖直方向捏一下,水平方向捏一下这样的效果。这其实就是一个组合动画,如下

上面代码中实现了从 圆 → 椭圆(x方向长轴)→ 椭圆(y方向长轴)→ 圆这一系列的变化,最后组合成一个动画。这一步实现后效果如下

四、实现圆形边缘的凸出部分

关于这个凸出部分,乍一看可能感觉会比较难实现,看起来挺复杂的。其实实现的原理很简单,仔细分析我们会发现这三个凸出部分连起来刚好是一个三角形,那么第一步我们就在之前的基础上先加一个三角形的layer,如下

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

}

}

然后设置圆角

lineCap = kCALineCapRound

lineJoin = kCALineJoinRound

下面就是来做凸出部分了,原理其实很简单,就是将现在这个三角形保持中心不变,左边向左延伸即可

然后同理,保持中心不变分别按顺序向右和向上拉伸

具体过程是这样的

/**

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)

/**

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)

}

旋转之后原图形被切成了一个三角形,思路就是把原来的大圆,按着这个大三角形的内切圆剪切一下即可

/**

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,划线

/**

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)

}

最后面就是经典的水波纹动画了,不多说,直接上代码

![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

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

// 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),你可以在 这里 查看源码!

一直对动画比较感兴趣,希望多研究多深入,有什么意见或者建议的话,可以留言或者私信,如果觉得还好的话,请star支持,谢谢!

iOS 复杂动画之抽丝剥茧的更多相关文章

  1. iOS复杂动画之抽丝剥茧(Objective-C & Swift)

    一.前言 随着开发者的增多和时间的累积,AppStore已经有非常多的应用了,每年都有很多新的APP产生.但是我们手机上留存的应用有限,所以如何吸引用户,成为产品设计的一项重要内容.其中炫酷的动画效果 ...

  2. iOS核心动画学习整理

    最近利用业余时间终于把iOS核心动画高级技巧(https://zsisme.gitbooks.io/ios-/content/chapter1/the-layer-tree.html)看完,对应其中一 ...

  3. IOS 核心动画之CAKeyframeAnimation - iBaby

    - IOS 核心动画之CAKeyframeAnimation - 简单介绍 是CApropertyAnimation的子类,跟CABasicAnimation的区别是:CABasicAnimation ...

  4. iOS各种动画效果

    ios各种动画效果 最普通动画: //开始动画 [UIView beginAnimations:nil context:nil];  //设定动画持续时间 [UIView setAnimationDu ...

  5. IOS之动画

    IOS之动画   15.1 动画介绍 15.2 Core Animation基础 15.3 隐式动画 15.4 显式动画 15.5 关键帧显式动画 15.6 UIView级别动画 15.1 动画介绍 ...

  6. IOS 动画专题 --iOS核心动画

    iOS开发系列--让你的应用“动”起来 --iOS核心动画 概览 通过核心动画创建基础动画.关键帧动画.动画组.转场动画,如何通过UIView的装饰方法对这些动画操作进行简化等.在今天的文章里您可以看 ...

  7. ios 学习动画的套路 (一)

    你也肯定喜欢炫酷的动画! 在APP中,动画就是一个点睛之笔!可以给用户增加一些独特的体验感,估计也有许多的和我一样的,看着那些觉得不错的动画,也就只能流口水的孩子,毕竟~不知道从哪里下手去写!会连续的 ...

  8. Bodymovin:Bodymovin和Lottie:把AE动画转换成HTML5/Android/iOS原生动画

    转自:https://www.cnblogs.com/zamhown/p/6688369.html 大杀器Bodymovin和Lottie:把AE动画转换成HTML5/Android/iOS原生动画 ...

  9. 大杀器Bodymovin和Lottie:把AE动画转换成HTML5/Android/iOS原生动画

    前段时间听部门老大说,Airbnb出了个移动端的动画库Lottie,可以和一个名叫Bodymovin的AE插件结合起来,把在AE上做好的动画导出为json文件,然后以Android/iOS原生动画的形 ...

随机推荐

  1. source insight 使用技巧

    一.在所有文件中查找字符串 1.菜单栏选择“search project” 2.在随便一个工程文件中把所要查找的字符串输入到空白的地方,然后点连接

  2. hello,world不使用ARC

    main.m // // main.m // Hello // // Created by lishujun on 14-8-28. // Copyright (c) 2014年 lishujun. ...

  3. 安卓天天练练(四)drawable state 属性

    今天又作茧自缚(item 写成 itme ╮(╯▽╰)╭ elipse还自动闭合了标签,来回查查查看报错,为啥点击无效呢!) 真欠抽,怪不得上班地铁上被个sb踢到脚趾头(目测有可能是同家公司的..同站 ...

  4. Qt中事件处理的方法(图文并茂,仔细看看)

    http://blog.csdn.net/qing666888/article/details/14111271 http://blog.csdn.net/qing666888/article/det ...

  5. Qt动画与Qt坐标小记

    Qt动画 转载自: <http://jingyan.baidu.com/article/154b46315757b628ca8f4116.html> 和  <http://blog. ...

  6. 使用bacula实现Linux的远程备份和还原

    Bacula,被誉为开源软件中最好的备份还原软件,它提供了企业级的客户机/服务器的备份解决方案,能够通过网络来管理文件的备份,恢复和核实工作.Bacula,既有windows版本的,也有Linux,U ...

  7. bzoj1211

    prufer码水题(n-2)!/[(d1-1)!*(d2-1)!*…*(dn-1)!] ..] of longint; x,n,i,j,s:longint; ans:int64; begin read ...

  8. bzoj2002

    这道题学习了一种简洁的解决一些数据结构题的方法——分块法这道题方法很多,但分块写起来只有1kb左右,非常的简洁(但不是非常的高效)首先很容易思考到一种暴力的做法,从后往前推,很容易搞出每个点会弹几次弹 ...

  9. 对于利用pca 和 cca 进行fmri激活区识别的理解

    1.pca 抛开fmri研究这个范畴,我们有一个超长向量,这个超长向量在fmri研究中,就是体素数据.向量中的每个数值,都代表在相应坐标轴下的坐标值.这些坐标轴所组成的坐标系,其实是标准单位坐标系.向 ...

  10. 如何配置jdk和tomcat 转

    一.配置JDK1.解压JDK至D:\JDK1.5目录下(楼主可以自由选取目录).2.设置环境变量(右键我得电脑->属性->高级->环境变量),在系统变量中添加一个叫JAVA_HOME ...