iOS Core Animation - Advanced Techniques-学习笔记(四)

经过之前学习,对于Core Animation除了动画外的特性有了一些了解。本篇开始,涉及到了框架最主要特性动画的相关知识

隐式动画

事务

  • Core Animation基于一个假设构建,屏幕上的任何东西都可以(或可能)做动画。动画不需要手动打开,但是要明确关闭,否则会一直存在

    这里会有一个问题,为什么更改UIView的属性,不会产生任何动画效果,若有需要必须用animation块包裹呢

  • 对于CALayer属性更改不需要特意开启动画效果,例如创建一个色块,然后随机改变它的颜色,你会发现图层的颜色是平滑过渡的,而不是跳变,这个过程的持续时间是0.25s

    因为我们并未指定特定的动画类型即可实现动画效果,所以叫隐式动画。隐式动画执行时间、执行内容等由CATransaction类进行管理,这个类不能实例化,但可以用+begin+commit控制入栈和出栈

  • 每一次Runloop周期,Core Animation都会开始一次新的CATransaction,即使不显示使用+begin入栈,在任何一次的Runloop循环中,属性的改变都会被集中起来,做一次animationDuration时间的动画

    注意:若使用+setAnimationDuration:方法来修改时间,最好压入一个新的Transaction,再修改时间,因为在当前事务的时间可能会导致同一时刻别的动画(屏幕旋转)时长被修改
    回到之前的问题,UIView默认关闭了隐式动画,但提供了+animateWithDuration:animations:事务块,这和我们对新建事务出入栈的写法是不是类似呢。其实他们是在做一样的事

  • UIView在动画块结束时允许提供一个完成动作,它的实现原理,其实就是使用了事务的+setCompletionBlock:方法

图层行为

  • 首先明确一个概念,我们把改变属性时CALayer自动应用的动画称作行为。我们知道UIView对于视图属性的修改,是即时响应的,也就是说UIKit禁用了关联图层的隐式动画,要了解UIKit怎么禁用的需要知道隐式动画的实现,实质上是以下几个步骤

    • 图层检查CALayerDelegate协议是否实现了-actionForLayer:forKey方法,如果设置了则直接返回结果
    • 如果没有实现协议或是-actionForLayer:forKey方法,图层继续检查包含属性名称对应行为的映射字典actions
    • 如果actions字典没有包含对应的行为,图层会接着在它的style字典搜索属性名
    • 如果style字典也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为方法-defaultActionForKey:
  • 通过以上几个Core Animation响应行为的流程,我们可以发现,隐式动画实际上是通过框架定义的标准行为方法-defaultActionForKey:返回实现的

  • UIView对实现了对应关联图层的CALayerDelegate协议,如果属性变化不在动画块中,则UIView对所有图层行为返回了nil

  • 禁用隐式动画还有一个方法,是使用事务类CATransacition的+setDisableActions:方法

总结:

  1. UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画
  2. 对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画

视觉呈现模型

  • 有经验的开发者可能接触过类似MVVM模型的Vue框架,当你在VM更改了M属性后,视图层会立即回应你的修改。回到CALayer,它的属性行为其实略微有些奇怪,当你改变一个图层的属性时,视图上并没有立即生效(可以自己打一个断点实验),而是一段时间后,通过渐变更新。

    通过之前的学习,我们现在可以容易的理解CALayer对属性渲染的流程,在更改属性后,runloop的下一个周期,会触发新的事务CATransaction,并查找这个属性的动画行为,如果未查到指定动画行为,则通过-defaultActionForKey:方法执行默认行为

  • 其实,这就是一个微型的MVC模型,Core Animation扮演了一个控制器的角色,负责图层行为和事务设置去不断更新视图

  • 在iOS中,屏幕每秒钟重绘60次(60fps)。如果动画时长比60分之一秒要长,Core Animation就需要在设置一次新值和新值生效之间,对屏幕上的图层进行重新组织。这意味着CALayer除了“真实”值(就是设置的值)之外,必须要知道当前显示在屏幕上的属性值的记录。presentationLayer记录了呈现图层的外观,可以通过这个属性获取屏幕上显示的真实值

    多数情况,我们不用访问呈现图层,但例如如果你想要知道,用户的触摸行为是否作用在运动中的图层,这时呈现图层会很有用

  • 书中有一个例子,有一个矩形图层,当我们点击图层时改变颜色,当点击图层外侧时移动图层

- (void)viewDidLoad{
    [super viewDidLoad];
    //create a red layer
    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
    self.colorLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
    self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //get the touch point
    CGPoint point = [[touches anyObject] locationInView:self.view];
    //check if we've tapped the moving layer
    if ([self.colorLayer.presentationLayer hitTest:point]) {
        //randomize the layer background color
        CGFloat red = arc4random() / (CGFloat)INT_MAX;
        CGFloat green = arc4random() / (CGFloat)INT_MAX;
        CGFloat blue = arc4random() / (CGFloat)INT_MAX;
        self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    } else {
        //otherwise (slowly) move the layer to new position
        [CATransaction begin];
        [CATransaction setAnimationDuration:4.0];
        self.colorLayer.position = point;
        [CATransaction commit];
    }
}

显式动画

CAAnimation是所有动画类的抽象父类,他提供了动画类的工厂方法,以及动画类所需协议。具体的动画行为我们需要实例化他的子类

  • CAPropertyAnimation
    • CABasicAnimation
    • CAKeyframeAnimation
  • CAAnimationGroup
  • CATransition

CAPropertyAnimation - 属性动画

  • 属性动画通过指定CALayer的可动画属性名称赋值给keyPath属性,并设置相应的初始值和结束值,以达到动画效果。我们不用关心动画中的渲染实现,《熟练的艺术家》会帮我们完成动画流程渲染
  • additive 如果这个属性为YES,则动画所指定值以添加的方式计算并得到一个新的值,仿射变化也会适用
  • cumulative 下一次动画是否接着上一次动画,默认为NO

CABasicAnimation - 基本动画

  • 基本动画的实现非常简单,他定义三个状态值属性,控制动画前和动画后CALayerkeyPath属性的状态
    • fromValue:keyPath相应属性的初始值
    • toValue:keyPath相应属性的结束值
    • byValue:keyPath相应属性的相对值
  • 下面代码描述了使用基本动画改变图层背景色
- (IBAction)changeColor
{
    //create a new random color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    //create a basic animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.toValue = (__bridge id)color.CGColor;
    animation.delegate = self;
    //apply animation to layer
    [self.colorLayer addAnimation:animation forKey:nil];
}
  • 上述代码,在fillMode=kCAFillModeForwardsremovedOnComletion=NO时,图层会保持动画执行后的状态,但是不要被视图渲染结果所欺骗,图层的属性值还是动画执行前的初始值,所以,我们在代码中还要实现CAAnimationDelegate的代理方法,更改图层的属性值
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
    //set the backgroundColor property to match animation toValue
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
    [CATransaction commit];
}

这里我们要禁用当前隐式动画事务,不然赋值最终值还要再被隐式动画再渲染一遍

  • 不得不承认,显示地给图层添加CABasicAnimation动画,相较于直接添加隐式动画,只能说费力不讨好

CAKeyframeAnimation - 关键帧动画

  • 关键帧动画依然作用于CALayer单一的一个属性,但他不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。你只需要提供显著表现的帧值,Core Animation会在每帧之间平滑插入过度表现
  • 一段简单的代码,表现了图层背景色在指定一系列颜色的变化
- (IBAction)changeColor
{
    //create a keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.duration = 2.0;
    animation.values = @[
                         (__bridge id)[UIColor blueColor].CGColor,
                         (__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor,
                         (__bridge id)[UIColor blueColor].CGColor ];
    //apply animation to layer
    [self.colorLayer addAnimation:animation forKey:nil];
}
  • 但用数组来描述动画的行为并不直观,所以关键帧动画有另外一种方式指定动画,就是使用path属性,它是CGPath类型,我们可以使用Core Graphics来绘制运动轨迹,但使用UIBezierPath类来绘制动画会更加简单
- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    ...
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 4.0;
    animation.path = bezierPath.CGPath;
    animation.rotationMode = kCAAnimationRotateAuto;
    [shipLayer addAnimation:animation forKey:nil];
}

注意这里指定了rotationMode旋转模板为自动,这样图层会根据曲线切线自动旋转

CAAnimationGroup - 动画组

  • 属性动画仅作用于单一属性的变化,CAAnimationGroup可以将这些动画组合在一起,变成复合动画
  • 可以使用animations属性,数组内的所有动画对象都是并发进行的
  • 可以通过设置数组内动画对象的beginTime属性改变开始时间

CATransition - 过渡动画

  • 属性动画只能对图层的可动画属性起作用,所以如果要动态改变(如图片)不能动画的属性,或者移除添加图层,属性动画将会失效
  • 过渡动画可以影响不能动画的属性,他首先展示之前的图层外观,然后通过指定的交换方式(type),平滑过渡到新的外观
  • 过渡动画通过type来定以动画效果,他是一个NSString类型
    • fade - kCATransitionFade 平滑淡化过渡(不支持过渡方向)
    • moveIn - kCATransitionMoveIn 新视图移到旧视图上面
    • push - kCATransitionPush 新视图把旧视图推出
    • reveal - kCATransitionReveal 将旧视图移开显示下面的旧视图
    • cube 立方体翻滚效果
    • oglFlip 上下左右翻转效果
    • suckEffect 收缩效果,如一块布被抽走(不支持过渡方向)
    • rippleEffect 滴水效果(不支持过渡方向)
    • pageCurl 向上翻页效果
    • pageUnCurl 向下翻页效果
    • cameraIrisHollowOpen 相机镜头打开效果(不支持过渡方向)
    • cameraIrisHollowClose 相机镜头关上效果(不支持过渡方向)
  • subtype属性指定动画的方向
    • kCATransitionFromRight
    • kCATransitionFromLeft
    • kCATransitionFromTop
    • kCATransitionFromBottom
  • startProgress动画起点(在整体动画的百分比)
  • endProgress动画终点(在整体动画的百分比)
  • 一个实例,切换UITabBarController标签时淡入淡出动画
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
    //set up crossfade transition
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    //apply transition to tab bar controller's view
    [self.tabBarController.view.layer addAnimation:transition forKey:nil];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336