您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页iOS - Core Animation 死亡笔记:初窥

iOS - Core Animation 死亡笔记:初窥

来源:二三娱乐
你若盛开,蝴蝶自来(图片来自网络)

一、隐式动画

1.图层是Core Animation的核心,那么什么是CALayer

  • OC中,基本上所有的控件都是UIView,而UIView之所以能够在屏幕上显示,因为每一个UIView内部都默认关联一个CALayer.
  • UIView将要显示到屏幕上的时候,会调用drawRect:进行绘图,将所有的内容绘制在自己的图层上,绘制完毕后,系统会将图层copy到屏幕上.其实也就是说UIView本身不具备显示的功能,是因为有了Layer才有显示的功能,所以UIView和CALayer相互依赖.

2.什么是隐式动画

  • Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在<code>Core Animation</code>中手动打开,相反需要明确地关闭,否则他会一直存在。
  • 所有手动创建的CALayer对象,都存在隐式动画,关于layer的属性,例如:
    1.bounds:用于设置CALayer的宽高.修改属性会产生缩放动画
    2.backgroundColor:设置背景色,会产生背景色的渐变动画
    3.position:位置的改变,默认会产生平移动画
    tips:以上都是隐式动画
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic,strong) CALayer *layer; 
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 手动创建layer
    self.layer = [CALayer layer];
    self.layer.frame = CGRectMake(0, 0, 200, 200);
    self.layer.backgroundColor = [UIColor blackColor].CGColor;
    [self.view.layer addSublayer:self.layer];
}
// 点击的时候更改layer的位置.(用于测试是否会产生隐式动画)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.layer.frame = CGRectMake(100, 100, 200, 200);
}
@end

那么如何关闭隐式动画呢?
CALayer隐式动画实际上是自动执行了CATransaction,CATransaction默认动画时间0.25s

// 点击的时候更改layer的位置.(用于测试是否会产生隐式动画)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
        self.layer.frame = CGRectMake(100, 100, 200, 200);
    [CATransaction commit];
}

效果图:

左:未关闭隐式动画<br>右:关闭隐式动画

将隐式动画时间改成2s,并改变layer的背景颜色

  • 通过+ setAnimationDuration:方法设置当前动画时间,或者通过+ animationDuration方法来获取值(默认0.25秒)
    [CATransaction begin];
    // 改变动画时间:设置为2s
    [CATransaction setAnimationDuration:2.0f];
    [CATransaction setDisableActions:NO];
    self.layer.frame = CGRectMake(100, 100, 200, 200);
    self.layer.backgroundColor = [UIColor yellowColor].CGColor;
    [CATransaction commit];

基于UIView的block的动画允许你在动画结束的时候提供一个完成的动作。CATranscation接口提供的+ setCompletionBlock:方法也有同样的功能

    [CATransaction setCompletionBlock:^{
        NSLog(@"完成了");
    }];
    [CATransaction commit];
    NSLog(@"将要完成");

二、显式动画

  • 隐式动画是在iOS平台创建动态用户界面的一种直接方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。
  • 显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。

属性动画 - CAPropertyAnimation

属性动画作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。属性动画分为两种:基础和关键帧。

  • 基础动画 - CABasicAnimation

  动画其实就是一段时间内发生的改变,最简单的形式就是从一个值改变到另一个值.
  CABasicAnimation 继承 CAPropertyAnimation, 而CAPropertyAnimation是c所有动画类型的一个抽象基类
  CAPropertyAnimation 通过指定动画的 KeyPath 作用于一个单一的属性.

  类型的转换
id obj = @(float) // CGFloat 类型转换
id obj = [NSValue valueWithCGPoint:point] // CGPoint
id obj = [NSValue valueWithCGsize:point] // CGsize
id obj = [NSValue valueWithCGPoint:point] // CGPoint
id obj = [NSValue valueWithCGRect:point] // CGRect


CABasicAnimation 继承 CAPropertyAnimation,添加了如下属性

```

id fromValue; // 动画开始前属性值
id toValue; // 动画结束后属性值
id byValue; // 动画过程中改变的值

  • 这里的 id 类型 可以作用于不同的属性,比如:'数字','矢量','颜色','图片';
  • 根据官方的头文件说明:这三个组合属性 可以有很多方式去组合.但是不能一次性同时指定'三个值',只需要指定'fromValue 和 toValue 或 byValue'
- 用于CAPropertyAnimation的一些类型转换



写代码啦~~~    ^ & ^


                        '通过CABasicAnimation来设置图层背景色'

import "ViewController.h"

@interface ViewController ()
@property (nonatomic,strong) CALayer *layer;
@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    self.layer = [CALayer layer];
    self.layer.frame = CGRectMake(50, 50, 200, 200);
    self.layer.backgroundColor = [UIColor blackColor].CGColor;
    [self.view.layer addSublayer:self.layer];
    }

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    CABasicAnimation animation = [CABasicAnimation animation];
    animation.duration = 3.f;
    ' /
    效果发现:一旦动画结束并从图层上移除之后,layer就立刻恢复到之前定义的外观状态。我们从没改变过backgroundColor属性,所以图层就返回到原始的颜色。

    • 如果需要动画完成后,layer的颜色不恢复到之前的外观,则需要加上下面这两行代码
      */

    // animation.fromValue = (__bridge id _Nullable)(self.layer.backgroundColor);
    // self.layer.backgroundColor = [UIColor redColor].CGColor;
    '
    animation.keyPath = @"backgroundColor";
    animation.toValue = (__bridge id _Nullable)([UIColor redColor].CGColor);
    [self.layer addAnimation:animation forKey:nil];
    }
    @end




> 有小伙伴提出怎么在动画结束后保持效果,在这里感谢:**"霸气侧漏"**的这位蜀黍提供的代码;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;


- CAAnimationDelegate
> 我们可以看到有两个方法.那么,优化上面的代码


  • (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
    {
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.layer.backgroundColor =(__bridge CGColorRef)((CABasicAnimation *)anim).toValue;
    [CATransaction commit];
    }
- 关键帧动画 - CAKeyframeAnimation

> CAKeyframeAnimation是另一种UIKit没有暴露出来但功能强大的类。和CABasicAnimation类似,CAKeyframeAnimation同样是CAPropertyAnimation的一个子类,它依然作用于单一的一个属性,但是和CABasicAnimation不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。

使用CAKeyframeAnimation 做颜色变化

@interface ViewController ()
@property (nonatomic,strong) CALayer *layer;
@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    self.layer = [CALayer layer];
    self.layer.frame = CGRectMake(50, 50, 200, 200);
    self.layer.backgroundColor = [UIColor blackColor].CGColor;
    [self.view.layer addSublayer:self.layer];
    }

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.duration = 2.0f;
    animation.values = @[(__bridge id)[UIColor redColor].CGColor,
    (__bridge id)[UIColor orangeColor].CGColor,
    (__bridge id)[UIColor yellowColor].CGColor,
    (__bridge id)[UIColor greenColor].CGColor,
    (__bridge id)[UIColor cyanColor].CGColor,
    (__bridge id)[UIColor blueColor].CGColor,
    (__bridge id)[UIColor purpleColor].CGColor];
    [self.layer addAnimation:animation forKey:nil];
    }

`CAKeyframeAnimation`不能自动把当前值作为第一帧(类似1于1CABasicAnimation把`fromValue`设为`nil`),所以为了动画的平滑特性,我们需要开始和结束的关键帧来匹配当前属性的值


CAKeyframeAnimation有另一种方式去指定动画,就是使用CGPath.可以结合UIBezierpath创建路径,进行做动画


- 根据UIBezierPath进行Animation

@interface ViewController ()
@property (nonatomic,strong) UIView *bezierPathView;
@property (nonatomic,strong) UIBezierPath *path;
@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    self.bezierPathView = [[UIView alloc] initWithFrame:(CGRectMake(10, 40, 80, 80))];
    self.bezierPathView.layer.cornerRadius = 50;
    self.bezierPathView.layer.masksToBounds = YES;
    self.bezierPathView.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:self.bezierPathView];

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:(CGPointMake(40, 80))];
    [path addLineToPoint:(CGPointMake(100, 300))];
    [path addCurveToPoint:CGPointMake(300, 300) controlPoint1:(CGPointMake(200, 0)) controlPoint2:(CGPointMake(325, 500))];
    self.path = path;

    CAShapeLayer *shaperLayer = [CAShapeLayer layer];
    shaperLayer.path = path.CGPath;
    shaperLayer.fillColor = [UIColor clearColor].CGColor;
    shaperLayer.strokeColor = [UIColor purpleColor].CGColor;
    path.lineWidth = 2.0f;
    [self.view.layer addSublayer:shaperLayer];
    }

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 5.0f;
    animation.path = _path.CGPath;
    // 通过rotation自动对齐图层到曲线

warning 如果不适用自动对齐,效果不会太逼真.可以自己尝试效果

animation.rotationMode = kCAAnimationRotateAuto;
[self.bezierPathView.layer addAnimation:animation forKey:nil];

}


这样一个路径图就构造出来了,^_^



- 虚拟属性

> 
- 属性动画实际上是针对关键路径而不是一个键,这就意味着可以对子属性甚至是虚拟属性进行做动画.
-  什么是虚拟属性? 
  如果想要对一个物体做旋转动画,应该用到transform属性,因为CALayer没有显示提供角度或者方向之类的属性

利用transform对layer做动画

import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong) UIView *animationView;
@property (nonatomic,strong) CALayer *layer;

@end

@implementation ViewController

  • (void)viewDidLoad
    {
    [super viewDidLoad];

    self.animationView = [[UIView alloc] initWithFrame:(CGRectMake(10, 40, 300, 300))];
    self.animationView.backgroundColor = [UIColor cyanColor];
    [self.view addSubview:self.animationView];

    CALayer *layer= [CALayer layer];
    layer.frame = CGRectMake(0, 0, 200, 200);
    layer.position = CGPointMake(150, 150);
    layer.contents = (__bridge id)[UIImage imageNamed:@"1.jpg"].CGImage;
    [self.animationView.layer addSublayer:layer];
    self.layer = layer;
    }

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    CABasicAnimation *animtion = [CABasicAnimation animation];
    animtion.keyPath = @"transform";
    animtion.duration = 3.0f;
    animtion.toValue = [NSValue valueWithCATransform3D:(CATransform3DMakeRotation(M_PI, 0, 0, 1))];
    [self.layer addAnimation:animtion forKey:nil];

}




**这里如果我们把M_PI(180°)调整到M_PI*2(360°),就会发现怎么都不会动了.因为:toValue的结果是360°,又回到了最初的状态,所以值根本没有变,没有动画效果,那么如何改变呢?**

- *改变 :  对 `transform.rotaion` 进行动画 *

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    CABasicAnimation *animtion = [CABasicAnimation animation];
    animtion.keyPath = @"transform.rotation";
    animtion.duration = 3.0f;
    // animtion.toValue = [NSValue valueWithCATransform3D:(CATransform3DMakeRotation(M_PI * 2, 0, 0, 1))];
    animtion.byValue = @(M_PI * 2);
    [self.layer addAnimation:animtion forKey:nil];
    }


> 为什么用`transform.rotation`?
1. 我们可以不通过关键帧一次旋转多余180°的动画
- 可以用相对值 (byValue)
- 可以用不用CATransform3D
- 不会和transform.positon以及transform.scale冲突,完全分开进行独立的动画



- 组动画 - CAAnimationGroup

> CAAnimationGroup可以把上面所述的动画类型进行组合在一起.
 - CAAnimationGroup 也是继承于CAAnimtion的子类
 - CAAnimationGroup 添加了 `animaitons` 属性

组合 `CAKeyframeAnimtion` 和 ` CABasicAnimation`

import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong) UIView *animationView;
@property (nonatomic,strong) CALayer *layer;
@property (nonatomic,strong) CAAnimationGroup *group;
@end

@implementation ViewController

  • (void)viewDidLoad
    {
    [super viewDidLoad];

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:(CGPointMake(50, 400))];
    [path addCurveToPoint:(CGPointMake(300, 100)) controlPoint1:(CGPointMake(200, 0)) controlPoint2:CGPointMake(245, 350)];

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.lineWidth = 2.0f;
    shapeLayer.strokeColor = [UIColor brownColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.path = path.CGPath;
    [self.view.layer addSublayer:shapeLayer];

self.layer = [CALayer layer];
self.layer.frame = CGRectMake(0, 0, 80, 80);
self.layer.position =CGPointMake(100, 150);
self.layer.cornerRadius = 40;
self.layer.masksToBounds = YES;
self.layer.backgroundColor = (__bridge CGColorRef _Nullable)((__bridge id)[UIColor whiteColor].CGColor);
;
[self.view.layer addSublayer:self.layer];

CALayer *imgLayer = [CALayer layer];
imgLayer.frame = CGRectMake(0, 0, 50, 50);
imgLayer.position = CGPointMake(40, 40);
imgLayer.contents = (__bridge id)[UIImage imageNamed:@"1.jpg"].CGImage;
[self.layer addSublayer:imgLayer];

// position
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
keyAnimation.keyPath = @"position";
keyAnimation.path = path.CGPath;
keyAnimation.rotationMode = kCAAnimationRotateAuto;

// transform.rotation
CABasicAnimation *basicAnimaiton = [CABasicAnimation animation];
basicAnimaiton.keyPath = @"transform.rotation";
basicAnimaiton.byValue = @(M_PI * 2);

// opacity
CAKeyframeAnimation *alphaAnimation = [CAKeyframeAnimation animation];
alphaAnimation.keyPath = @"opacity";
alphaAnimation.values = @[@0.3,@0.5,@0.8,@0.1,@.4,@.9,@0.1,@.5,@.2,@.7,@.3,@1.0];

// backgroupColors
CAKeyframeAnimation *colorAnimtion = [CAKeyframeAnimation alloc];
colorAnimtion.keyPath = @"backgroundColor";
colorAnimtion.values = @[(__bridge id)[UIColor whiteColor].CGColor,
                         (__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor orangeColor].CGColor,
                         (__bridge id)[UIColor yellowColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor,
                         (__bridge id)[UIColor cyanColor].CGColor,
                         (__bridge id)[UIColor blueColor].CGColor,
                         (__bridge id)[UIColor purpleColor].CGColor,
                         (__bridge id)[UIColor whiteColor].CGColor];

//groupAnimation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[keyAnimation,alphaAnimation,colorAnimtion,basicAnimaiton];
groupAnimation.duration = 5.0;

self.group = groupAnimation;

}

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    [self.layer addAnimation:self.group forKey:nil];
    }



- 过渡动画 - CATransition

> - `CATransition` 同样继承于 `CAAnimation` 
 
- CATransition 在 API中已经有很清楚的说明了.他有一个`type`,`subtype`来标记变换效果


简单的imageView加载图片

@interface ViewController ()

@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,strong) NSArray *imageArr;

@end

@implementation ViewController

  • (void)viewDidLoad
    {
    [super viewDidLoad];

    self.imageView = [[UIImageView alloc] initWithFrame:(CGRectMake(100, 100, 100, 100))];
    [self.view addSubview:self.imageView];
    self.imageView.image = [UIImage imageNamed:@"1.jpg"];

    self.imageArr = @[[UIImage imageNamed:@"1.jpg"],
    [UIImage imageNamed:@"2.jpg"],
    [UIImage imageNamed:@"3.jpg"],
    [UIImage imageNamed:@"4.jpg"]];

}

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    [self.imageView.layer addAnimation:transition forKey:nil];

    UIImage *current = self.imageView.image;
    NSUInteger index = [self.imageArr indexOfObject:current];
    index = (index + 1) % self.imageArr.count;
    self.imageView.image = self.imageArr[index];
    }



>- 隐式过渡
当设置了CALayer的`content`属性的时候,`CATransition`是默认的行为。但对于UIView关联的图层,或者是其他隐式动画,这个特性依然是被禁用的,但是对于自己创建的`Layer`,`layer.contents`图片都会自动附上`Fade`的动画。

- 使用UIKit提供的动画方法做过渡动画

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    UIImage *current = self.imageView.image;
    NSUInteger index = [self.imageArr indexOfObject:current];
    index = (index + 1) % self.imageArr.count;

    [UIView transitionWithView:self.imageView duration:1.0 options:(UIViewAnimationOptionTransitionFlipFromTop) animations:^{
    self.imageView.image = self.imageArr[index];
    } completion:NULL];
    }

   


 Tips: 最初的梦想,紧握在手上,让我们在最美的时光里,做最好的自己!


Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务