一、前言


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

HWLoadingAnimation.gif

二、分析


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

  • 1、先出来一个圆
  • 2、圆形在水平和竖直方向上被挤压,呈椭圆形状的一个过程,最后恢复成圆形
  • 3、圆形的左下角、右下角和顶部分别按顺序凸出一小部分
  • 4、圆和凸出部分形成的图形旋转一圈后变成三角形
  • 5、三角形的左边先后出来两条宽线,将三角形围在一个矩形中
  • 6、矩形由底部向上被波浪状填满
  • 7、被填满的矩形放大至全屏,弹出Welcome

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

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


首先,我们创建了一个新工程后,然后新建了一个名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)
}

运行效果如下

CircleLayer.gif

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

    /**
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方向长轴)→ 圆这一系列的变化,最后组合成一个动画。这一步实现后效果如下

WobbleAnimation.gif

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


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

addTriangleLayer.png

然后设置圆角

        lineCap = kCALineCapRound
lineJoin = kCALineJoinRound

roundTriangle.png

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

TriangleLayerAnimate.gif

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

TriangleLayerAnimateGroup.gif

具体过程是这样的

    /**
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)
}

我们接下来把三角形的颜色改一下

TriangleLayerAnimateGroup2.gif

这里颜色相同了我们就可以看到了这个凸出的这个效果,调到正常速率(为了演示,把动画速率调慢了) ,联合之前所有的动作,到现在为止,效果是这样的

FrontAnimateGroup.gif

到现在为止,看上去还不错,差不多已经完成一半了,继续下一步!

五、实现旋转和矩形


旋转来说很简单了,大家估计都做过旋转动画,这里就是把前面形成的图形旋转一下(当然要注意设置锚点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)
}

RotationAnimation.gif

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

    /**
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)
}

ContractAnimation.gif

接下来就是画矩形,新建一个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)
}

RectangletAnimation.gif

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

    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)
}

WavetAnimation.gif

找几个点控制水波形状,画出贝塞尔曲线即可,到这里基本就完成了。接下来最后一步,放大,并弹出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()
}) }

到现在为止,动画全部完成

HWLoadingAnimation.gif

六、最后


同样,还是提供了两个版本(Objective-C & Swift),你可以在 这里 查看源码!
一直对动画比较感兴趣,希望多研究多深入,有什么意见或者建议的话,可以留言或者私信,如果觉得还好的话,请star支持,谢谢!

文/HenryCheng(简书作者)
原文链接:http://www.jianshu.com/p/658641c77f51
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

iOS复杂动画之抽丝剥茧(Objective-C & Swift)的更多相关文章

  1. iOS 复杂动画之抽丝剥茧

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

  2. 关东升的《iOS实战:图形图像、动画和多媒体卷(Swift版)》上市了

    关东升的<iOS实战:图形图像.动画和多媒体卷(Swift版)>上市了 承蒙广大读者的厚爱我的<iOS实战:图形图像.动画和多媒体卷(Swift版)>京东上市了,欢迎广大读者提 ...

  3. Objective C & Swift & iOS & App

    Objective C & Swift & iOS & App https://www.runoob.com/ios/ios-objective-c.html https:// ...

  4. iOS核心动画高级技巧之图层变换和专用图层(二)

    iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...

  5. iOS核心动画高级技巧之CALayer(一)

    iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...

  6. iOS天气动画、高仿QQ菜单、放京东APP、高仿微信、推送消息等源码

    iOS精选源码 TYCyclePagerView iOS上的一个无限循环轮播图组件 iOS高仿微信完整项目源码 想要更简单的推送消息,看本文就对了 ScrollView嵌套ScrolloView解决方 ...

  7. iOS核心动画学习整理

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

  8. IOS 核心动画之CAKeyframeAnimation - iBaby

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

  9. iOS各种动画效果

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

随机推荐

  1. sharepoint 浏览页面导航不正确

    问题是这样的: sharepoint网站上建立一个二级站点,然后在网站中创建几个页面.当浏览二级网站的页面的时候,顶部导航的位置总是在首页的地方,而不是我的二级站点的导航位置. 解决方法: 转到网站集 ...

  2. 一个关于C#中基类与接口混合继承的疑问总结

    思路参照 http://www.cnblogs.com/allenlooplee/archive/2004/11/16/64553.html,对原文进行了简化和补充,感谢原作者. 问题很简单,如下所示 ...

  3. 硬件相关-ADC原理(未完成)

    一.模数转换的一般步骤: 1)采样和保持 为了把模拟信号转换成对应的数字信号,必须首先将模拟量每隔一定时间抽取一次样值,使时间上连续变化的模拟量变为一个时间上断续变化的模拟量,这个过程称为采样. 为了 ...

  4. snmp数据包分析

    今天看了一下snmp数据包的报文格式,用wireshark抓了两个数据包来分析. 先说说snmp get-request的书报包格式吧,get-next-request,get-response,se ...

  5. 玩耍Hibernate之缓存

    2.在持久化层,对象分为哪些状态?分别列出来. 答:瞬时态(Transient).持久态(Persistent).脱管态(Detached). 瞬时态(Transient) 是对象是创建时,瞬时对象在 ...

  6. IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)

    ... 首先了解一下CGContextRef: An opaque type that represents a Quartz 2D drawing environment. Graphics Con ...

  7. 2015最新百度搜索引擎(seo优化)排名算法

    多少年来,对于弄清百度排名算法成为了一代又一代站长的最高目标.随着百度推出了搜索引擎网页质量**,直接揭开了神秘的百度排名算法,这是作为站长福音啊.现在小编就来为大家介绍一下. 首先想要得到直接需要的 ...

  8. Java Synchronized Blocks vs. Methods

    It's possible to synchronize both an entire method and a section of code within a method, and you ma ...

  9. SQL 语法 Join与Union

    问题描述: Join与Union使用 问题解决: Join连接,可以分为: tableA如下: tableB如下: 1.1.Inner Join SELECT * FROM TableA INNER ...

  10. Sprite Kit 入门教程

    Sprite Kit 入门教程  Ray Wenderlich on September 30, 2013 Tweet 这篇文章还可以在这里找到 英语, 日语 If you're new here, ...