浅析iOS Core Animation

原文地址

简介

分析的知识点:

  • layer tree
  • CGAffineTransform
  • addArc
  • calculationMode
  • CAAnimationGroup
  • UIView.animate animateKeyframes、CABasicAnimation、CAKeyframeAnimation

提到Core Animation,就会想到CALayer
修改一个View的layer后,会用Core Graphics框架来渲染。
Core Animation有三个tree:

  • model layer tree(简称layer tree),存储动画的最终状态值,当改变layer的属性值时,使用这一个
  • presentation layer tree,在动画过程中,使用这个来获取当前动画过程中的值,但不要改变他的值
  • rendering tree,最后一个对Core Animation而言是私有的。
一个window中的layers
window的layer trees

UIView常用动画

透明度变化:

    UIView.animate(withDuration: 0.3, animations: {
      self.pandaImageView.alpha = 1
    }) { _ in
      self.pandaImageView.removeFromSuperview()
    }

或者更改一下frame,直接这样调用,使用非常方便。再看下面这一个,放大缩小再复原:

UIView.animate(withDuration: 0.3, animations: {
      self.pandaImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
    }) { _ in
      UIView.animate(withDuration: 0.3, animations: {
        self.pandaImageView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
      }, completion: { _ in
        UIView.animate(withDuration: 0.3, animations: {
          self.pandaImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
        }, completion: { _ in
          self.view.removeFromSuperview()
        })
      })
    }

感觉也没啥问题,就是嵌套多了一点,也有点丑,针对这种嵌套,可以这样解决:

let totalTime = toBig + toSmall + dismissView
UIView.animateKeyframes(withDuration: totalTime, delay: 0, options: [], animations: {
      UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: toBig/totalTime, animations: {
        self.frontView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25)
      })
      UIView.addKeyframe(withRelativeStartTime: toBig/totalTime, relativeDuration: toSmall, animations: {
        self.frontView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
      })
      UIView.addKeyframe(withRelativeStartTime: (toBig + Animation.toSmall)/totalTime, relativeDuration: dismissView, animations: {
        self.alpha = 0
      })
    }) { _ in
      self.removeFromSuperview()
    }

: withRelativeStartTimerelativeDuration 这两个值都是0.0到1.0之间的数值, 需要除以总动画时间。

这种比刚才的写法要好很多,不用嵌套多层,但解决不了复杂的动画。如果在做放大缩小前还有别的动画呢,比如先沿特定曲线路径移动,再放大缩小,可能这种写法就满足不了,对于复杂动画构建及时间函数控制,看下文。

基本动画

让一个图片做直线运动:

func lineAnimation() {
    let animation = CABasicAnimation()
    animation.keyPath = "position.x";
    animation.fromValue = kScreenWidth/2;
    animation.toValue = kScreenWidth;
    animation.duration = 1;
    pandaImageView.layer.add(animation, forKey: "line")
    pandaImageView.layer.position = CGPoint(x: kScreenWidth, y: kScreenHeight/2)
  }

动画的键路径"position.x",在x轴上的直线运动,其它的键可以查看Core Animation键路径的列表
动画开始后,图片从中心位置移到了屏幕边缘,动画结束后,图片回到了中心点。要想让动画结束后保持最后的状态,有两种方法:

  • model layer上更新属性,推荐这种,这样presentation layermodel layer就同步了
  • 设置动画的 fillMode 属性为 kCAFillModeForward 以留在最终状态,并设置removedOnCompletionNO 以防止它被自动移除
    采用第二种方法会将已完成的动画保持在layer上,有额外开销,还需要考虑何时需要手动remove掉,比如在应用切后台时。

沿路径动画

做有弧度的曲线或者圆形动画时,可以用CAKeyframeAnimation,指定path,逆时针做90度的圆弧运动:

    let keyframeAnimation = CAKeyframeAnimation(keyPath: "position")
    let path = CGMutablePath()
    path.addArc(center: CGPoint(x: 0, y: 0), radius: radius, startAngle: 0, endAngle: 1.5 * .pi, clockwise: true)
    keyframeAnimation.path = path
    keyframeAnimation.calculationMode = kCAAnimationPaced
    keyframeAnimation.duration = 2.0;
    pandaImageView.layer.add(keyframeAnimation, forKey: "round")
    pandaImageView.layer.position = CGPoint(x: 0, y: 0)
CoreAnimation-point.png

CGAffineTransform

pandaImageView做了一个CGAffineTransform(translationX: kScreenWidth/2, y: kScreenHeight/2), CGAffineTransform为一个变换,可以把一个坐标系映射到另外一个坐标系,平移、放缩、旋转都可以做到,详细请看, CGAffineTransform默认的anchor point为中心点(0.5, 0.5),这里我们不用改,正是想要的,在应用上这个transform后, addArc圆的圆心就变成了(0,0)

addArc

addArcclockwise,有如下解释:

true to make a clockwise arc; false to make a counterclockwise arc.

clockwise为true时,path的方向为逆时针,而算角度,始终以逆时针方向算。

calculationMode

calculationMode能决定动画的时间曲线, kCAAnimationPaced为匀速,其它的几种mode可以查看官方文档,默认为kCAAnimationLinear.

A->B->C->D->A,连起来的动画,需要一个path:

    path.addLine(to: CGPoint(x: radius, y: 0))
    path.addArc(center: CGPoint(x: 0, y: 0), radius: radius, startAngle: 0, endAngle: 1.5 * .pi, clockwise: true)
    path.addLine(to: CGPoint(x: -radius/2, y: 0))
    path.addLine(to: CGPoint(x: 0, y: 0))

放缩动画

    let keyframeAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
    keyframeAnimation.values = [1.2, 0.8, 1]
    keyframeAnimation.duration = 1.0;
    keyframeAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]

关于timing function,看下图:

timing-function.png

动画组

   let roundAnimation = self.roundAnimation()
    let scaleAnimation = self.scaleAnimation()
    scaleAnimation.beginTime = roundAnimation.duration
    let groupAnimation = CAAnimationGroup()
    groupAnimation.animations = [roundAnimation, scaleAnimation]
    groupAnimation.duration = roundAnimation.duration + scaleAnimation.duration
    pandaImageView.layer.add(groupAnimation, forKey: "group")

指定好开始时间和持续时间,可以使用一个动画组
动画效果如图:


group-animation.gif

对于开篇提到的问题,用动画组合可以很好的解决问题

参考文章

objccn动画解释
Core Animation Programming Guide

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

推荐阅读更多精彩内容