iOS | CoreAnimation介绍和简单案例

本篇的Demo地址:
github.com/tanghaitao/…
假如想修改frame,angle等,即简单的动画的话,直接修改相关属性就行。
但是想做复杂的动画的,就需要使用核心动画CoreAnimation.
本篇将以一种由浅入深的方式带领大家一起学习CoreAnimaton(同时也希望大佬能提出宝贵的意见)

CoreAnimation中核心的类是CALayer,
先引入一个知识点: CALayer和UIView的区别:

image.png

Layer的隐式动画/显示动画

  • 隐式动画:默认时间0.25s(位置,颜色,大小),必须是独立的layer(不是uiview.layer)才有隐式动画,uiview(根layer)是没有隐式动画的
  • 显示动画:下图中的动画,需要实例化具体的类才能显示对应效果的画。

下面的CoreAnimaton的结构图:

image.png

Layer的一些概念:
image.png

KeyPath属性设置:

image.png

 动画三步骤:
 1.初始化动画对象
 2.设置需要修改的动画属性的值
 3.把动画添加到layer(所有的动画都是添加在layer上,不是view)

 `你看到的都是假象,真正的view是没有发生变化的`
 postition:(layer里面设置中心点) center:uiview
 //redview宽高100,layer.position.y移动到400后,uiview.frame.y= 400-100/2 = 350
 presentationLayer和modelLayer(呈现层和模型层),属性设置先修改模型层的数据,Vsync来的时候呈现层再从模型层获取数据,重绘一次。

 锚点:1.概念;2.单位坐标0-1;3.和position的关系:就是锚点在父视图的位置

 隐式动画:默认时间0.25s(位置,颜色,大小),必须是独立的layer(不是uiview.layer)才有隐式动画,uiview(根layer)
 显示动画:
复制代码

1 基础动画CABasicAnimation

Demo地址:github.com/tanghaitao/…
下面从例子进行讲解:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CABasicAnimation *anim = [CABasicAnimation animation];
    anim.keyPath = @"position.y";        
    anim.toValue = @400;
    [_redView.layer addAnimation:anim forKey:nil];    
}
复制代码

QQ20210528-155720.gif
上面演示效果,动画结束后没有停在结束位置,而是回到了原来的位置了。

    anim.removedOnCompletion = NO;//默认会回到最初的位置
    anim.fillMode = kCAFillModeForwards;//默认会回到最初的位置
复制代码

添加上面两行代码后的效果, 就会保持动画结束时的状态

QQ20210528-160152.gif

如果想在动画开始之前或者结束的时候做一些额外的操作,就需要使用代理:
image.png

@interface ViewController ()<CAAnimationDelegate>
 anim.delegate = self;//动画开始和结束时
- (void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"start-%@",NSStringFromCGRect(_redView.frame));
}

//hittest
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"stop-%@",NSStringFromCGRect(_redView.frame));
    // NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.presentationLayer.frame));
   // NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.modelLayer.frame));
}

 start-{{0, 0}, {100, 100}}
 stop-{{0, 0}, {100, 100}}
复制代码

为什么动画结束的时候打印的是 stop-{{0, 0}, {100, 100}}

 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
[_redView addGestureRecognizer:tap];
- (void)tapAction:(UITapGestureRecognizer *)ges{
    NSLog(@"redview被点击了");
}
复制代码

程序运行点击redview,打印redview被点击了
动画结束点击redview,不打印 redview被点击了

//NSLog(@"stop-%@",NSStringFromCGRect(_redView.frame));// stop-{{0, 0}, {100, 100}}
//NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.frame));// stop-{{0, 0}, {100, 100}}
 NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.presentationLayer.frame));// stop-{{0, 349.99860227108002}, {100, 100}}
//NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.modelLayer.frame));// stop-{{0, 0}, {100, 100}}
复制代码

_redView.layer.presentationLayer.frame 呈现的位置

2 关键帧动画CAKeyframeAnimation

Demo地址:
github.com/tanghaitao/…

    #define angleToRadians(angle) ((angle) / 180.0 *M_PI)//角度转弧度
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"transform.rotation";
    anim.values = @[@angleToRadians(-6), @angleToRadians(6)];
    anim.repeatCount = MAXFLOAT;
    anim.duration = 2;//默认是0.5秒
复制代码

效果如下:

3.gif
可以看出, 从开始到结束 是正常的,但是 从结束到开始不正常,马上回去了0.5,duration不是持续时间。
解决办法:

  • anim.values = @[@angleToRadians(-6),@angleToRadians(6),@angleToRadians(-6)];
  • anim.autoreverses = YES;//反转, 同时设置 anim.speed = 2;, 反转后速度会变慢 (推荐使用这种方式)

效果如下:

4.gif

2.1 案例讲解:过山车动画

UIBezierPath贝塞尔曲线:利用公式把数据绘制成曲线
绘制有三个关键信息: 落笔point 连线 addline 抬笔point
复制代码

最终效果:

5.gif

开始讲解思路:

2.1 画贝塞尔曲线

/// 画贝塞尔曲线
-(void)test21{
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:CGPointMake(50, 200)];
    
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
//    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(112.5, 100) controlPoint2:CGPointMake(237.5, 300)];
    //(50,200)最左边的点
    // (300,200) 终点
    // (180, 100) 顶部 (200, 300) 底部
    // width: 300-50 = 250,长度分别为1/2= 125,1/4= 62.5,中点位置是50+125 = 175 .
    // 左边中点  175-1/4 = 175-62.5 = 112.5
    // 右边中点  175+1/4 = 175+62.5 = 237.5
    
    // height: 小于200,顶部, 大于200,底部
    
    //path添加到layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;// ---------------- 这里
    [self.view.layer addSublayer:shapeLayer];
}
复制代码

效果展示:

Simulator Screen Shot - iPhone 12 Pro Max - 2021-05-28 at 18.25.58.png

2.2 去掉填充颜色,设置画笔颜色

/// 去掉填充颜色,以及设置画笔颜色
-(void)test22{
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:CGPointMake(50, 200)];
    
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    
    //path添加到layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
    [self.view.layer addSublayer:shapeLayer];
}

复制代码

效果展示:

Simulator Screen Shot - iPhone 12 Pro Max - 2021-05-28 at 18.28.49.png

2.3 绘制过山车图片

/// 绘制过山车图片
-(void)test23 {
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:CGPointMake(50, 200)];
    
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    
    //这里 begin
    CALayer *carLayer = [CALayer layer];
    carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
    carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
    [self.view.layer addSublayer:carLayer];
   //这里 end 
   
    //path添加到layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
    [self.view.layer addSublayer:shapeLayer];
}
复制代码

效果展示:

Simulator Screen Shot - iPhone 12 Pro Max - 2021-05-28 at 18.30.39.png

2.4 添加过山车动画

/// 添加过山车动画 动起来,添加动画属性
-(void)test24  {
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:CGPointMake(50, 200)];
    
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    
    
    CALayer *carLayer = [CALayer layer];
    carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
    carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
    [self.view.layer addSublayer:carLayer];
    
    //path添加到layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
    [self.view.layer addSublayer:shapeLayer];
    
    // 过山车动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.path = path.CGPath;//路径,不是values
    anim.keyPath = @"position";//改变position
    anim.duration = 5.0f;
    [carLayer addAnimation:anim forKey:nil];
}
复制代码

效果展示:

6.gif

2.5 车角度没有跟着曲线走

/// 过山车的角度问题,角度没有跟着曲线走
-(void)test25  {
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:CGPointMake(50, 200)];
    
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    
    
    CALayer *carLayer = [CALayer layer];
    carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
    carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
    [self.view.layer addSublayer:carLayer];
    
    carLayer.anchorPoint = CGPointMake(0.5, .8);//改变车的角度
    
    //path添加到layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
    
    
    [self.view.layer addSublayer:shapeLayer];
    
    // 过山车动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.path = path.CGPath;
    anim.keyPath = @"position";
    anim.duration = 5.0f;
    
    anim.rotationMode = kCAAnimationRotateAuto; // --------2
    [carLayer addAnimation:anim forKey:nil];
}
复制代码

效果展示:

7.gif

2.6 停留在结束位置

/// 停留在结束位置
-(void)test26  {
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:CGPointMake(50, 200)];
    
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    
    
    CALayer *carLayer = [CALayer layer];
    carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
    carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
    [self.view.layer addSublayer:carLayer];
    
    carLayer.anchorPoint = CGPointMake(0.5, .8);//改变车的角度
    
    //path添加到layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
    
    
    [self.view.layer addSublayer:shapeLayer];
    
    // 过山车动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.path = path.CGPath;
    anim.keyPath = @"position";
    anim.duration = 5.0f;
    
    anim.rotationMode = kCAAnimationRotateAuto;
    
    //动画结束
    anim.removedOnCompletion = NO;
    anim.fillMode = kCAFillModeForwards;
    
    [carLayer addAnimation:anim forKey:nil];
}
复制代码

效果展示:

8.gif

上面是整个的分析过程,可以根据效果来分析每一步操作。

3 转场动画CATransition

补充一个知识点 frame和bounds.关于这2个属性的区分,举个简单例子,就好比一个人睡觉,笔直的睡觉时,frame和bounds是一样的,但如果这个人斜着睡觉,那么frame就会变大,bounds是自身的。类似下面的图: 旋转后frame = {-1,3,62,64},bounds={0,0,40,50};

image.png

Demo地址:
github.com/tanghaitao/…

转场类型:

image.png

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    _index++;
    if (_index == 3) {
        _index = 0;
    }
    NSString *imgName = _imgs[_index];
    _imgView.image = [UIImage imageNamed:imgName];
    CATransition *anim = [CATransition animation];
    anim.type = @"cube";
    anim.duration = .5;
//    anim.startProgress = .2;//转场开始时机
//    anim.endProgress = .5;//转场结束时机:一半的时候就结束转场动画
    [_imgView.layer addAnimation:anim forKey:nil];
    
}
复制代码

效果展示:

9.gif

4 动画组CAAnimationGroup

顾名思义就是多个动画同时执行

Demo地址:
github.com/tanghaitao/…
ViewController.m 文件的 test1() 方法中:

- (void)test1{
    //    曲线
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 200)];
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    //需要添加在layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = nil;
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:shapeLayer];
    
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 60, 60);
    colorLayer.position = CGPointMake(50, 200);
    colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:colorLayer];
    
    //    过山车的动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"position";
    anim.path = path.CGPath;
//    anim.duration = 4.0f;
//    anim.removedOnCompletion = NO;
//    anim.fillMode = kCAFillModeForwards;
//    anim.rotationMode = kCAAnimationRotateAuto;
//    [colorLayer addAnimation:anim forKey:nil];
//    改变大小
    CABasicAnimation *sizeAnim = [CABasicAnimation animation];
    sizeAnim.keyPath = @"transform.scale";
    sizeAnim.toValue = @.5;
//    sizeAnim.duration = 4.0;
//    sizeAnim.fillMode = kCAFillModeForwards;
//    sizeAnim.removedOnCompletion = NO;
//
//    [colorLayer addAnimation:sizeAnim forKey:nil];
    
//    修改颜色
    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];
    
    CABasicAnimation *colorAnim = [CABasicAnimation animation];
    colorAnim.keyPath = @"backgroundColor";
    colorAnim.toValue = (id)color.CGColor;
//    colorAnim.duration = 4.0f;
//    colorAnim.fillMode = kCAFillModeForwards;
//    colorAnim.removedOnCompletion = NO;
//    [colorLayer addAnimation:colorAnim forKey:nil];
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = @[anim, sizeAnim, colorAnim];
    //重复的代码可以放到动画组里面。
    group.duration = 4.0f;
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    [colorLayer addAnimation:group forKey:nil];
    
}
复制代码

效果展示:

10.gif

//    过山车的动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
//    改变大小
CABasicAnimation *sizeAnim = [CABasicAnimation animation];
//    修改颜色
CABasicAnimation *colorAnim = [CABasicAnimation animation];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[anim, sizeAnim, colorAnim];

 //重复的代码可以放到动画组里面。
group.duration = 4.0f;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;

这里添加了三个动画,同时改变,需要使用动画组CAAnimationGroup
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享