【iOS动画】学习笔记第二弹(Layer Animation)

本文是笔者学习iOS动画的一些小总结,接第一弹

Layer Animation

第一弹中主要是关于View Animation 的一系列操作,今天的主角当然得是Layer啦,其实Layer Animation 工作并不复杂,我们只需要选择一个是animatable的属性,然后设置开始值、结束值、动画时间,之后系统就会让Core Animation去对应的layer渲染,产生动画效果。

CALayer相比UIView有更多的animatable的属性,所以使用Layer Animation可以更好的写出自己想要的动画效果,同时CALayer还有很多特定的子类,常见的如下所示:CAShapeLayer, CATextLayer, CAGradientLayer, CAReplicatorLayer….. 这些不同的子类,又包含不同的animatable的属性,所以根据特定的子类,可以简单的写出很酷炫的动画。下面来看一个栗子:

 let springMoveLeftAnimation = CABasicAnimation(keyPath: "position.x")
 springMoveLeftAnimation.fromValue = -view.frame.width
 springMoveLeftAnimation.toValue = view.frame.width / 2.0
 springMoveLeftAnimation.duration = 0.7
 springMoveLeftAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
 colorView.layer.add(springMoveLeftAnimation, forKey: nil)

上述栗子创建了一个springMoveLeftAnimation,这个动画对象是可以加到任意多个layer上去的,所以使用Layer Animation 创建的动画是可以重复利用的。上述栗子中只是简单的将一个view从左移动到屏幕中间。

真正的动画

上栗中,我们在colorView的layer上增加了一个动画,其实我们看到的动画并不是真正作用在colorView,我们看到的只是一个所谓的presentation layer,当动画结束后presentation layer就会在屏幕中被移除,真正的colorView重新显示到屏幕上。

所以上栗中,colorViewx原本不是居中的,经过动画后,colorViewx依然不是居中的,会移动到原本的位置。

所以想要colorViewx保持动画后的模样,我们可以设置如下代码:

springMoveLeftAnimation.fillMode = kCAFillModeBoth
springMoveLeftAnimation.isRemovedOnCompletion = false
fillMode

fillModeCAMediaTiming协议中一个属性,用来控制动画序列的开始和结束的行为,默认是kCAFillModeRemoved,默认效果如下图所示:

屏幕快照 2017-09-20 15.36.00.png

如果想要延时执行某个动画,可以设置beginTime属性

springMoveLeftAnimation.beginTime = CACurrentMediaTime() + 0.3 
/// 根据CACurrentMediaTime()取得动画执行的时间,然后我们增加了0.3秒的延时
  • kCAFillModeBackwards
屏幕快照 2017-09-20 15.43.24.png

如图所示,设置fillModekCAFillModeBackwards,不论是否设置延时,都会提前显示动画的第一帧。

  • kCAFillModeForwards

屏幕快照 2017-09-20 15.46.24.png

如图所示,设置fillModekCAFillModeForwards,当动画被移除之前会保留动画的最后一帧。

  • kCAFillModeBoth

屏幕快照 2017-09-20 15.49.05.png

如图所示,设置fillModekCAFillModeBoth,相当于是上面两个属性的结合。

当动画结束后会保留最后一帧,然后设置isRemovedOnCompletionfalse,所以动画就不会被移除,这样colorView就能够保持动画后的模样。但是现在因为不是真正的colorView,所以就不能做任何操作了,当colorView为输入框时,此时因为是presentation layer,也就不能响应用户的输入。而且这样做也会有性能问题,所以不建议设置isRemovedOnCompletionfalse

此时还有一个有效的方法是,通常我们可以在colorView加入动画后,直接更新colorViewx,这样在动画结束后,colorView的位置就保持和动画后一致。

控制动画

第一弹中我们通过UIKit帮我们封装的UIView语法的动画一旦创建运行,我们是不能暂停或者停止的。但是Layer Aniamtion提供了相关的API,让我们可以更近一步的去控制我们所创建的动画。

animation delegate

我们可以设置CAAnimation的代理,通过代理提供的方法来控制动画。

 func animationDidStart(_ anim: CAAnimation)
 func animationDidStop(_ anim: CAAnimation, finished flag: Bool)

CAAnimationDelegate提供了上述两个代理方法,通过提供的anim参数从而可以控制动画,但是如果多个动画都设置了代理,这时如何区分不同的动画做不同的事情呢?

因为CAAnimation 和其子类是用OC写的,而且支持KVC,所以我们可以用func setValue(_ value: Any?, forKey key: String)方法来设置不同key给不同的动画,这样在代理中就能区别不同的动画了。

通过设置代理的方式我们只能控制刚开始和刚结束时期的动画,那么如何控制正在运行的动画呢,此时就需要用到func add(_ anim: CAAnimation, forKey key: String?)方法中的key参数,我们可以在该动画开始后通过设置的key参数来控制对应的动画了。

 open func removeAllAnimations()
 open func removeAnimation(forKey key: String)

我们可以通过以上两个方法来对操作正在运行的动画,哈哈,只是简单的移除😂
并不能动态的改变动画运行路径。

我们可以设置动画的speed属性控制动画速度,同时也可以通过设置layer的speed属性从而使layer上添加的所有动画设置速度,此时如果layer里某个动画自身也设置了速度,那么此时的速度会根据view的层级进行乘积;

组合动画

我们可以在layer上添加多个动画,如果想要控制不同动画之间的同步,此时需要用到CAAnimationGroup

    // added animation group
    let groupAnimation = CAAnimationGroup()
    groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
    groupAnimation.beginTime = CACurrentMediaTime() + 0.5
    groupAnimation.duration = 0.5
    groupAnimation.fillMode = kCAFillModeBackwards
    
    let scaleDown = CABasicAnimation(keyPath: "transform.scale")
    scaleDown.fromValue = 3.5
    scaleDown.toValue = 1.0
    let rotate = CABasicAnimation(keyPath: "transform.rotation")
    rotate.fromValue = .pi / 4.0
    rotate.toValue = 0.0
    let fade = CABasicAnimation(keyPath: "opacity")
    fade.fromValue = 0.0
    fade.toValue = 1.0
    
    groupAnimation.animations = [scaleDown, rotate, fade]
    loginButton.layer.add(groupAnimation, forKey: nil)

基本的layer animation 差不多就是这些,可能不是面面俱到,但是其他的一些属性,可以通过查看头文件里的属性即可,下面学习下layer animation里的spring animation;

弹性动画

第一弹中我们就已经接触过layer animation,但是UIKit帮我们封装了,并没有深入的了解其中的细节,所以在layer 层中的spring animation我们可以进一步的研究下spring animation 的细节。

首先我们想象下钟摆,当我们给它一个力,此时如果没有摩擦的话,那么钟摆就会永远的摆动,但是实际由于和空气之间产生摩擦,所以钟摆最后一定会停止。而且不同质量的钟摆从运动到停止的时间是不一样的,最后还和重力有关系,同样的钟摆在月球上的运动轨迹和地球是有很大的差距的。

其实钟摆来回摆动的这个效果就是spring animation 的效果。此时我们可以根据上述总结出如下属性是影响spring animation的:

1. damping: 摩擦相关, 默认是10.0
2. mass: 质量,默认是1.0
3. stiffness: 重力相关, 默认是100.0
4. initialVelocity: 初始速度,默认是0.0

上述的四个属性就是CASpringAnimation所提供的,其中damping,initialVelocity我们在UIKit提供的spring animation 中就已经使用过,UIKit会根据我们所给的duration来计算出其他两个属性的,从而产生对应的spring animation,所以有时候会感觉有点不真实。但我们现在可以使用CASpringAnimation所提供的四个属性来创建自己想要的同时更加真实的spring animation, 但是这种方式的缺点是,时间是不确定的,因为spring animation 从开始到停止的时间是根据你所提供的四个影响参数来计算出的。

所以我们设置spring animation的 duration时,需要使用spring animation的settlingDuration属性,这个时间就是根据你所提供的四个影响参数来计算出的;

            let flash = CASpringAnimation(keyPath: "borderColor")
            flash.damping = 7.0
            flash.stiffness = 200.0
            flash.fromValue = UIColor(red: 1.0, green: 0.27, blue: 0.0, alpha: 1.0).cgColor
            flash.toValue = UIColor.white.cgColor
            flash.duration = flash.settlingDuration
            textField.layer.add(flash, forKey: nil)

上述代码创建了一个spring animation 改变文本输入框的borderColor,要想做出自己想要的spring 动画,只需要不断的调整上述的四个属性即可。

keyframe动画

UIKit 也提供了keyframe animation,但是和layer层的keyframe animation 相比是有区别的。UIKit的keyframe animation,是用来做动画的连接的,可以在animations中创建多个keyframe动画,这些动画可以是设置在不同的view的不同属性。

之前我们设置在layer上的basic animation, 我们都会设置fromValuetoValue,通过设置的duration,Core Animation会自动的根据设置的value在给定的时间内改变layer的某个属性,于是动画产生了,而layer的keyframe animation则提供了让我们自己控制动画某个属性的过程的功能:

    let flight = CAKeyframeAnimation(keyPath: "position")
    flight.duration = 12.0
    flight.values = [
      CGPoint(x: -50, y: 0.0),
      CGPoint(x: view.frame.width + 50.0, y: 160.0),
      CGPoint(x: -50.0, y: loginButton.center.y)]
        .map { NSValue(cgPoint: $0) }
    
    flight.keyTimes = [0.0, 0.5, 1.0]
    balloon.add(flight, forKey: nil)

如上代码所示,我们通过values,keyTimes,自己可以控制动画的运行轨迹。

当创建的layer animation的keyPath是结构体时,需要使用NSValue进行包装;

Specialized Layers

前面所说的Layer Animation,我们都是为CALayer的一些基本属性做动画的,但是如果想要做出一些其他的酷炫的动画,哪些基本的可能满足不了要求,好在CALayer有很多有用的子类,我们通过它们的属性做动画,效果就会大不一样。

CAShapeLayer

CAShapeLayer通过你传入的pathvector graphics来画你所定义的的图形。下面看个栗子:

        let squarePath = UIBezierPath(rect: bounds)
        let animation = CABasicAnimation(keyPath: "path")
        animation.duration = 0.25
        animation.fromValue = circleLayer.path
        animation.toValue = squarePath.cgPath
        circleLayer.add(animation, forKey: nil)
        circleLayer.path = squarePath.cgPath
        
        maskLayer.add(animation, forKey: nil)
        maskLayer.path = squarePath.cgPath

和之前的layer animation一样,同样的设置fromValuetoValue,不过这次animation 的keyPath为CAShapeLayerpath属性。

CAShapeLayerstrokeEnd属性同样是animatable,所以根据这个属性可以做出一个画的过程的动画:

        let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
        pathAnimation.duration = 10.0
        pathAnimation.fromValue = 0.0
        pathAnimation.toValue = 1.0
        pathLayer?.add(pathAnimation, forKey: "strokeEnd")

完整的Demo下载地址CAShapeLayerAnimationDemo

CAShapeLayer还有一个功能就是设置圆角,只需要设置一个shapeLayer作为layer 的mask属性即可。

CAGradientLayer

CAGradientLayer可以通过设置多种颜色画出新的渐变的效果,而且CAGradientLayer有四个属性是animatable的:

startPoint, endPoint 为单元坐标系

colors: 渐变效果的颜色数组;
locations: 每种颜色的在渐变中的占比;
startPoint: 开始的点;
endPoint: 结束的点;

iOS之前的滑动进行解锁的动画就可以使用CAGradientLayer来实现:

WechatIMG1.png

完整的Demo下载地址CAGradientLayerAnimationDemo

CAReplicatorLayer

CAReplicatorLayer通常用来生成重复layer的集合,这样比手动添加效率更高。但是CAReplicatorLayer可以轻松的改变每个克隆layer的属性,让他们和他们的父亲不一样。最后CAReplicatorLayer有一个特别的属性instanceDelay,当你设置该属性为0.1秒同时在原layer上加了一个动画,此时CAReplicatorLayer生成的第一份克隆会延迟0.1秒执行动画,第二份克隆则会延迟0.2秒,第三份克隆则会延迟0.3秒,以此类推。通过这个特性我们可以写出一些复杂的动画效果。以下是CAReplicatorLayer三个重要的属性:

instanceCount: 设置你想要的克隆个数;
instanceTransform: 设置每个克隆和上一个克隆的差距;
instanceDelay: 设置每个克隆和上一个克隆的动画延迟;

利用CAReplicatorLayer上述特性,我们可以发挥想象做出一些酷炫的动画,例如下面这个动画:

WechatIMG3.jpeg

完整的Demo下载地址CAReplicatorLayerAnimationDemo

最后

未完待续第三弹

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容