玩转iOS开发:7.《Core Animation》Implicit Animations

文章分享至个人我的技术博客: https://cainluo.github.io/14807833712288.htmlhtml


做者感言

在上一篇文章《Core Animation》CALayer的Specialized Layers中, 咱们了解了CALayer的许多子类特性, 能够为咱们在遇到一些特殊的开发需求中提供必定的帮助, 既然咱们此次学的的Core Animation, 那怎么会和动画不挂钩呢? 此次让咱们来初体验一下.git

** 最后:** ** 若是你有更好的建议或者对这篇文章有不满的地方, 请联系我, 我会参考大家的意见再进行修改, 联系我时, 请备注**`Core Animation`**若是以为好的话, 但愿你们也能够打赏一下~嘻嘻~祝你们学习愉快~谢谢~**

简介

Implicit Animations也称为隐式动画, 啥? 什么叫作隐式动画? 百度去吧~~哈哈哈(后面会讲解的), 这些问题就不在这里作解释了, 仍是进入主题才比较重要.github


Transactions

其实在Core Animation中, 动画效果并不须要咱们去手动打开, 由于系统默认就是Open状态, 相反过来, 若是咱们不须要动画的话, 咱们须要手动的去关闭. 若是咱们用一个CALayer的一个动画属性, 而且尝试去改变它, 这个效果并不会立刻就显示出来, 由于它要从一个默认值平滑的过分到一个新的值, 而这些全部的内部操做咱们都不须要去理会, 由于系统默认就是这么作的. 咱们能够先来看个Demo:vim

- (void)transactionsColor {
    
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,
                                                            100,
                                                            self.view.frame.size.width,
                                                            self.view.frame.size.width)];
    
    view.backgroundColor = [UIColor grayColor];
    
    [self.view addSubview:view];
    
    UIButton *button = [[UIButton alloc] init];
    
    button.center = CGPointMake(self.view.frame.size.width / 2, 50);
    button.bounds = CGRectMake(0, 0, 100, 50);
    button.backgroundColor = [UIColor blueColor];
    
    [button setTitle:@"改变颜色"
            forState:UIControlStateNormal];
    [button addTarget:self
               action:@selector(changeLayerColor)
     forControlEvents:UIControlEventTouchUpInside];
    
    [view addSubview:button];
    
    self.colorLayer  = [CALayer layer];
    self.colorLayer.position = CGPointMake(view.frame.size.width / 2, view.frame.size.height / 2);
    self.colorLayer.bounds = CGRectMake(0, 0, 150, 150);
    self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
    
    [view.layer addSublayer:self.colorLayer];
}

- (void)changeLayerColor {
    
    CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
    
    self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
                                                      green:greenColor
                                                       blue:blueColor
                                                      alpha:1.0f].CGColor;
}
复制代码

1

2

看完这个Demo其实就已经知道神马叫作隐式动画了, 所谓的隐式动画就是咱们没有给它指定任何的动画类型, 仅仅只是改变某个属性, 固然Core Animation也是支持显示动画, 否则咱们就没那么多的兴趣来学习Core Animation了~ 那么当咱们去改变一个属性的时候, Core Animation是如何去判断动画类型还有动画的持续时间呢? 这个问题其实也很简单, 动画的执行时间取决于Transactions的设置, 而动画类型是取决于CALayer的行为. 其实Transactions其实是Core Animation用来包含一堆属性动画集合的机制, 任何用指定Transactions去改变能够作动画效果的图层属性都不会立刻发生变化, 而是须要Transactions在提交的一瞬间, 才会开始用一个动画效果过渡到新设置的值. 而Transactions是须要经过CATransaction这个类来进行管理的, 奇怪的是, CATransaction这个类并非管理一个简单的Transactions, 而是管理了一堆咱们不能访问的Transactions, 因为CATransaction并无属性和实例化方法, 也不能用**+ (instancetype)alloc;- (instancetype)init;方法来建立它, 只有它所提供的+ (void)begin;+ (void)commit;来控制. 虽然咱们再上面的Demo里没有设置动画时间, 但Core Animation会在每个run looop周期中自动开始一次新的Transactions**, 即便咱们不手动的去调用**[CATransaction begin];, 但在每一次run loop**的循环中, 被修改的属性都会集中起来, 而后统一作一次0.25秒的动画, 这个是系统默认的. 说了那么多, 咱们实际上来改改修改颜色的那个代码块, 让它有一个显示动画的效果:微信

- (void)changeLayerColorAgain {
    
    [CATransaction begin];
    [CATransaction setAnimationDuration:2.0f];
    
    CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
    
    self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
                                                      green:greenColor
                                                       blue:blueColor
                                                      alpha:1.0f].CGColor;
    
    [CATransaction commit];
}
复制代码

3

看起来的效果让人以为是真的有动画效果了, 若是你们在以前就已经用过UIView来作过动画的话, 那么你们应该对这个动画模式不会感受到陌生, 由于UIView就有两个相似的方法, + (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;+ (void)commitAnimations;, 其实这两个这两个方法也是由于在内部设置了CATransaction的缘由. 在iOS 4的时候, 苹果就已经对UIView添加了一种基于Block的动画方法, + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0);, 用起来更加的方便, 但其实是作一样的事情, 但使用这种方法就能够避免**+ (void)begin;+ (void)commit;**匹配的问题形成一些蛋疼的事情.dom


Completion Blocks

这里咱们就使用一下基于UIViewBlock动画方法, 咱们能够在动画结束以后再对这个图层进行一些操做, 固然这里仍是基于上面的Demo来作演示:ide

- (void)changeLayerColorWithCompletion {
    
    [CATransaction begin];
    [CATransaction setAnimationDuration:2.0f];
    
    [CATransaction setCompletionBlock:^{
        CGAffineTransform transform = self.colorLayer.affineTransform;
        
        transform = CGAffineTransformRotate(transform, M_PI_4);
        
        self.colorLayer.affineTransform = transform;
    }];
    
    CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
    
    self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
                                                      green:greenColor
                                                       blue:blueColor
                                                      alpha:1.0f].CGColor;
    
    [CATransaction commit];
}
复制代码

4


Layer Actions

开始的时候咱们就用一个Demo来进行演示:函数

- (void)addLayerView {
    
    self.layerView = [[UIView alloc] init];
    self.layerView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    self.layerView.bounds = CGRectMake(0, 0, 150, 150);
    self.layerView.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:self.layerView];
    
    UIButton *button = [[UIButton alloc] init];
    
    button.center = CGPointMake(self.view.frame.size.width / 2, 200);
    button.bounds = CGRectMake(0, 0, 100, 50);
    button.backgroundColor = [UIColor blueColor];
    
    [button setTitle:@"改变颜色"
            forState:UIControlStateNormal];
    
    [button addTarget:self
               action:@selector(changeLayerViewColor)
     forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:button];
}

- (void)changeLayerViewColor {
    
    [CATransaction begin];
    [CATransaction setAnimationDuration:2.0f];
    
    CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
    
    self.layerView.layer.backgroundColor = [UIColor colorWithRed:redColor
                                                           green:greenColor
                                                            blue:blueColor
                                                           alpha:1.0f].CGColor;
    
    [CATransaction commit];
}
复制代码

5

看完这个Demo, 有不少人确定会有疑问, 为啥没有了以前的那个平滑过渡效果呢? 好像是被干掉了, 这是啥回事? 其实咱们能够仔细想想, 若是UIView里的属性都有动画特性的话, 那咱们去修改这些属性时, 确定会注意到的, 可为啥UIKit要把这个隐式动画给禁止呢? 咱们都知道Core Animation一般会对CALayer全部的可作动画的属性都赋予了动画特性, 但在UIView中就不同了, 它会默认把所关联在一块儿的CALayer的这个特性给关闭掉, 这里就要了解一下隐式动画是如何实现的. 当咱们改变CALayer属性时, CALayer自动应用的动画, 咱们能够成为CALayer的行为, 每当CALayer的属性被修改的时候, 它会去调用**- (nullable id)actionForKey:(NSString *)event;**这个方法去传递属性的名称, 而后就会去执行以下几步:oop

  • 首先CALayer会去检测它是否有Delegate, 而且看看这个Delegate有没有实现CALayerDelegate协议里的**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;**方法, 若是有, 就直接调用并返回结果.布局

  • 若是CALayer没有Delegate的话, 或者Delegate没有实现**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;方法, 那么图层就会接着去检查包含属性名称对应的CALayer行为所映射的Actions**字典.

  • 若是Actions字典没有包含对应的属性, 那么图层接着会在它的style字典里接着搜索属性名.

  • 最后, 在style里也找不到对应的行为, 那么图层就会直接调用**+ (nullable id)defaultActionForKey:(NSString *)event;实现系统所提供的每一个属性的默认行为. 若是一轮完整的搜索结束以后, - (nullable id)actionForKey:(NSString *)event;返回为空的话, 那么确定不会有动画效果, 若是返回CAAction协议对应的对象, CALayer会拿这个结果去对比先前和当前的值, 而且作一个动画效果. 知道这个原理以后, 咱们就知道UIKit**是肿么把隐式动画给禁止掉了:

  • 每个UIView对它所关联的图层都是充当一个Delegate对象, 而且提供了**- (nullable id)actionForKey:(NSString *)event;**的实现方法.

  • 当不在一个动画块的实现中, 那么UIView就会对全部CALayer的行为返回nil, 若是在动画的Block范围以内, UIView就会返回一个非空的值. 这里咱们简单的Log一下结果:

- (void)checkViewAction {
    
    UIView *layerView = [[UIView alloc] init];
    
    layerView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    layerView.bounds = CGRectMake(0, 0, 150, 150);
    layerView.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:layerView];
    
    NSLog(@"Before: %@", [layerView actionForLayer:layerView.layer
                                            forKey:@"backgroundColor"]);
    
    [UIView beginAnimations:nil
                    context:nil];
    
    NSLog(@"After: %@", [layerView actionForLayer:layerView.layer
                                           forKey:@"backgroundColor"]);
    
    [UIView commitAnimations];
}
复制代码
2016-12-04 12:45:28.178 7.ImplicitAnimations[57079:2126402] Before: <null>
2016-12-04 12:45:28.179 7.ImplicitAnimations[57079:2126402] After: <CABasicAnimation: 0x6000000327c0>
复制代码

6

这样子咱们就能够知道, 当属性在Block以外发生改变, UIView会直接经过返回nil来禁用隐式动画, 但若是在动画块的范围以内, 就会根据动画的具体类型来返回相应的属性, 这个后续会讲到. 其实除了经过返回nil并非惟一禁止隐式动画的方法, 咱们也能够经过CATransacition的**+ (void)setDisableActions:(BOOL)flag;方法, 经过flag来对全部属性打开或者关闭隐式动画, 哪怕你是在[CATransaction begin];以后来添加, 也是同样能够关闭的. 这里还有一个Demo**, 使用CATransaction来实现的一个叫作推动过渡动画, 其实说白也就是一个Push动画:

- (void)pushAnimation {
    
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,
                                                            100,
                                                            self.view.frame.size.width,
                                                            self.view.frame.size.width)];
    
    view.backgroundColor = [UIColor grayColor];
    
    [self.view addSubview:view];
    
    UIButton *button = [[UIButton alloc] init];
    
    button.center = CGPointMake(self.view.frame.size.width / 2, 50);
    button.bounds = CGRectMake(0, 0, 100, 50);
    button.backgroundColor = [UIColor blueColor];
    
    [button setTitle:@"改变颜色"
            forState:UIControlStateNormal];
    
    [button addTarget:self
               action:@selector(pushChangeColor)
     forControlEvents:UIControlEventTouchUpInside];
    
    [view addSubview:button];
    
    self.colorLayer  = [CALayer layer];
    self.colorLayer.position = CGPointMake(view.frame.size.width / 2, view.frame.size.height / 2);
    self.colorLayer.bounds = CGRectMake(0, 0, 150, 150);
    self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
    
    CATransition *transition = [CATransition animation];
    
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;
    
    self.colorLayer.actions = @{@"backgroundColor": transition};
    
    [view.layer addSublayer:self.colorLayer];
}

- (void)pushChangeColor {
    
    [CATransaction begin];
    [CATransaction setAnimationDuration:2.0f];

    CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
    CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
    
    self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
                                                      green:greenColor
                                                       blue:blueColor
                                                      alpha:1.0f].CGColor;
    [CATransaction commit];
}
复制代码

7


Presentation Versus Model

其实仔细想一想, CALayer的属性行为并不太正常, 为什么这么说呢, 由于当咱们去改变一个图层的属性时, 咱们会发现, 这个值的确是当即发生了改变, 但在屏幕上并无立刻生效, 为什么呢? 由于咱们在设置属性的时候, 并无直接去调整图层的显示外观, 仅仅只是定义了图层动画结束以后即将要发生改变的外观. Core Animation在这里充当了一个控制器的角色, 而且根据Layer ActionsTransactions来更新视图在屏幕上显示的状态. 在于用户交互的界面中, CALayer的行为更像是保存着视图如何去显示和动画的执行数据模型. 在iOS中, 屏幕会以每秒钟重绘60次, 若是动画市场比60分之一秒还要长, 那么在这段时间里, Core Animation就会对屏幕上的图层进行从新的组合, 这就意味着CALayer除了咱们给予的值以外, 还必需要知道当前显示在屏幕上的属性值的记录. 而每一个图层属性的显示值都会被存储在一个叫作呈现图层的独立图层当中, 咱们能够经过**- (nullable instancetype)presentationLayer;方法来访问, 而这个所谓的呈现图层**, 实际上就是模型图层的复制, 但它的好处是它的属性值表明了在任何指定时间当前所显示的外观效果, 通俗点来说, 就是咱们能够经过获取呈现图层的值来获取当前屏幕上真正显示出来的值. 这里须要注意的一点就是, 若是在呈现图层仅仅当CALayer首次被提交的时候建立, 那么去调用**- (nullable instancetype)presentationLayer;方法就会返回nil**. 这里咱们或许还会注意到另外一个方法**- (instancetype)modelLayer;, 若是咱们在呈现图层上调用这个方法, 那么就会返回一个它正在呈现因此来的CALayer**, 而一般在一个图层上调用这个方法, 就会返回self. 在大多数开发的场景下, 咱们都不须要直接访问呈现图层, 咱们能够经过和模型图层的交互, 来让Core Animation更新而且显示, 但在如下两种场景下呈现图层就很是有用了, 一个是在同步动画里, 一个是在处理用户交互的时候:

  • 若是咱们在实现一个基于定时器的动画, 而不只仅是基于Transactions的动画, 这个时候咱们就要准确的知道在某一时刻图层显示在什么位置, 这就会对正确的布局起很是大的做用了.
  • 若是咱们想让作动画的图层对于用户有交互, 咱们可使用**- (nullable CALayer *)hitTest:(CGPoint)p;方法来判断指定的图层是否被点击了, 这个时候就会显示更加的友好, 由于呈现图层表明了用户当前看到的图层位置, 而不是当动画效果结束以后的位置. 说了那么多, 仍是直接上Demo**比较直接:
- (void)presentationVersusModel {
    
    self.colorLayer  = [CALayer layer];
    self.colorLayer.position = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    self.colorLayer.bounds = CGRectMake(0, 0, 150, 150);
    self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
    
    [self.view.layer addSublayer:self.colorLayer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CGPoint point = [[touches anyObject] locationInView:self.view];
    
    if ([self.colorLayer.presentationLayer hitTest:point]) {
        
        CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
        CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
        CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
        
        self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
                                                          green:greenColor
                                                           blue:blueColor
                                                          alpha:1.0f].CGColor;
    } else {
        [CATransaction begin];
        [CATransaction setAnimationDuration:4.0f];
        
        self.colorLayer.position = point;
        
        [CATransaction commit];
    }
}
复制代码

8


总结

总结一下:

  • Core Animation默认是打开动画效果的, 而且默认的动画效果是平滑过渡滴.
  • 咱们知道了隐式动画的实现方式.
  • UIView关联的图层默认都禁用了隐式动画, 对这种图层作动画的惟一办法就是使用UIView的动画函数, 或者是继承与UIView而且重写**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;**方法, 最直接的方法就是直接建立一个显示动画.
  • 对于一个单独存在的图层来说, 咱们能够经过实现图层的**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;方法, 或者是提供一个Actions**的字典来控制隐式动画.
  • 除此以外, 咱们来了解了呈现图层模型图层, 知道了这两个家伙的一些皮毛. 好了, 此次就到这里了, 谢谢你们~

工程地址

项目地址: https://github.com/CainRun/CoreAnimation


最后

码字很费脑, 看官赏点饭钱可好

微信

支付宝
相关文章
相关标签/搜索