一、前言

随着开发者的增多和时间的累积,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启动效果复杂动画之抽丝剥茧的更多相关文章

  1. Hover.css:一组超实用的 CSS3 悬停效果和动画

    Hover.css 是一套基于 CSS3 的鼠标悬停效果和动画,这些可以非常轻松的被应用到按钮.LOGO 以及图片等元素.所有这些效果都是只需要单一的标签,必要的时候使用 before 和 after ...

  2. CodePen 作品秀:Canvas 粒子效果文本动画

    作品名称——Shape Shifter,基于 Canvas 的粒子图形变换实验.在页面下方的输入框输入文本,上面就会进行变换出对应的粒子效果文本动画. CodePen 作品秀系列向大家展示来自 Cod ...

  3. 基于canvas实现物理运动效果与动画效果(一)

    一.为什么要写这篇文章 某年某月某时某种原因,我在慕课网上看到了一个大神实现了关于小球的抛物线运动的代码,心中很是欣喜,故而写这篇文章来向这位大神致敬,同时也为了弥补自己在运动效果和动画效果制作方面的 ...

  4. html5--6-55 动画效果-关键帧动画

    html5--6-55 动画效果-关键帧动画 实例 @charset="UTF-8"; div{ width: 150px; height: 150px; font-size: 2 ...

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

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

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

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

  7. 怎样做一个iOS App的启动分层引导动画?

    一. 为什么要写这篇文章? 这是一个很古老的话题,从两年前新浪微博开始使用多层动画制作iOS App的启动引导页让人眼前一亮(当然,微博是不是历史第一个这个问题值得商榷)之后,各种类型的引导页层出不穷 ...

  8. css3 3d效果及动画学习

    css参考手册: http://www.phpstudy.net/css3/ http://www.css88.com/book/css/ 呈现3d效果:-webkit-transform-style ...

  9. Android程序启动加载动画实现

    package com.example.bmob_test.ui;//程序启动动画,图片颜色由浅到深,方法一 import com.example.bmob_test.LogActivity; imp ...

随机推荐

  1. 剑指Offer——中国银行面试知识储备

    剑指Offer--中国银行面试知识储备+面试内容 事件介绍 时间:2016.11.23 08:30 地点:北京市海淀区永丰路299号南门(中国银行软件中心) 事件:中国银行面试(中英文面试) 注意事项 ...

  2. C语言如何在两个文件中访问同一个全局变量

    方法一: 不使用头文件. 1.c 中 int var; 2.c 中 extern int var; 方法二: 使用头文件. 1.c 中 int var; 不必添加#include "1.h& ...

  3. cuda网格的限制

    限制于计算能力有关. 详情 http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#compute-capabilities 只 ...

  4. java虚拟机 jvm 栈数据区

    java栈帧还是需要一些数据支持常量池的解析.正常方法的返回和异常的处理.大部分的java字节码指令需要进行常量池的访问,在栈帧数据区中保存着访问常量池的指针,方便程序访问java常量池.如下图所示: ...

  5. iOS开发之Xcode常用调试(Debug)技巧总结

    一.Xcode调试技巧之:NSLog 上面也提到了,在我们日常的开发过程中最常见的Debug方式就是打Log.而在OC语言中,打Log是采用NSLog方法.但是NSLog效率低下,具体原因可以看这篇博 ...

  6. Android开发学习之路--Annotation注解简化view控件之初体验

    一般我们在写android Activity的时候总是会在onCreate方法中加上setContentView方法来加载layout,通过findViewById来实现控件的绑定,每次写这么多代码总 ...

  7. DBCP连接池TestOnBorrow的坑

    生产环境连接池TestOnBorrow设置为false,导致有时获取的连接不可用.分析如下: TestOnBorrow=false时,由于不检测池里连接的可用性,于是假如连接池中的连接被数据库关闭了, ...

  8. Cocos2D iOS之旅:如何写一个敲地鼠游戏(三):素材最终解决方法

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  9. Swift基础之:新的访问控制fileprivate和open

    (转载的,暂时没有研究过这类语句,有空看看) 在swift 3中新增加了两种访问控制权限 fileprivate和 open.下面将对这两种新增访问控制做详细介绍. fileprivate 在原有的s ...

  10. String压缩 解压缩

    数据传输时,有时需要将数据压缩和解压缩,本例使用GZIPOutputStream/GZIPInputStream实现. 1.使用ISO-8859-1作为中介编码,可以保证准确还原数据 2.字符编码确定 ...