美文网首页
iOS Core Animation - Advanced Te

iOS Core Animation - Advanced Te

作者: 优质胡萝北 | 来源:发表于2021-01-04 22:07 被阅读0次

    经过之前学习,对于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];
    }
    

    相关文章

      网友评论

          本文标题:iOS Core Animation - Advanced Te

          本文链接:https://www.haomeiwen.com/subject/basboktx.html