美文网首页
iOS开发-核心动画(CAAnimation)相关

iOS开发-核心动画(CAAnimation)相关

作者: Super超人 | 来源:发表于2017-07-30 15:10 被阅读0次

    此文章单方面对 贝塞尔曲线画图 核心动画 图层相关 方面做下整理,方便查看!

    基本概念

    Core Animation(核心动画)是一组功能强大,在开发中可以用他来实现很多复杂和绚丽的动画效果,核心动画作用在CALayer(Core animation layer)上

    结构

    盗图 -_-.png

    代码和效果

    /**
     *  fillMode 视图在非Active时的行为
     *
     *  kCAFillModeForwards 动画开始之后layer迅速移到动画开始的位置
     *  kCAFillModeBackwards 动画被添加的那一刻(动画开始之前)layer迅速移到开始位置,并且在 removedOnCompletion 为 NO 的情况下,动画结束会移到layer本身的位置
     *  kCAFillModeBoth 动画添加的那一刻(动画开始之前)layer迅速移到开始位置,并且在 removedOnCompletion 为 NO 的情况下,动画结束layer会停留在动画结束的位置
     *  kCAFillModeRemoved  动画开始之后layer迅速移到动画开始的位置,并且在 removedOnCompletion 为 NO 的情况下,动画结束回忆道layer本身位置
     */
        
    /**
     *  timingFunction  动画节奏 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
     *
     *  kCAMediaTimingFunctionLinear            匀速
     *  kCAMediaTimingFunctionEaseIn            慢进
     *  kCAMediaTimingFunctionEaseOut           慢出
     *  kCAMediaTimingFunctionEaseInEaseOut     慢进慢出
     *  kCAMediaTimingFunctionDefault           默认值(慢进慢出)
     */
        
    /**
     *  removedOnCompletion 动画执行完毕后是否从图层上移除 默认为 YES (动画结束layer移到本身位置)
     */
        
    /** 
     *  这样个要配合使用 repeatDuration = repeatCount * duration(动画一遍持续时间,主要控制速度)
     *  repeatCount     动画重复执行次数
     *  repeatDuration  动画重复执行时间
     */
    
    CABasicAnimation
    1.位置相关动画 (position.y 和 position.x transform.translation.x 和 transform.translation.y)

    position fromValue:默认自身位置为起始位置 toValue:移动后的位置
    transform.translation fromValue:默认自身位置为起始位置(默认0) toValue:相对于layer原始位置距离

    • position.y 和 position.x
    /// y轴方向移动  fromValue:默认自身位置为起始位置 toValue:移动后的位置
    - (void)animation_CABasicAnimation_position_y
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
        //animation.fromValue = @(self.img.center.y); 默认初始位置
        animation.toValue = @(self.img.center.y + 100);
        animation.duration = 3;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [self.img.layer addAnimation:animation forKey:@"position.y"];
    }
    
    position.x/y.gif
    • transform.translation.x 和 transform.translation.y
    - (void)animation_CABasicAnimation_translation_x
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
        //animation.fromValue = @0; 默认初始位置
        animation.toValue = @100;
        animation.duration = 1;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [self.img.layer addAnimation:animation forKey:@"transform.translation.x"];
    }
    
    transform.translation.x/y.gif
    2.旋转相关动画
    • transform.rotation.x | transform.rotation.y | transform.rotation.z
    transform.rotation.x.gif transform.rotation.y.gif transform.rotation.z.gif
    3.缩放相关动画
    • transform.scale.x | transform.scale.y | transform.scale.z
    - (void)animation_CABasicAnimation_transform_scale
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        animation.toValue = @8.0;
        animation.duration = 3;
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [self.img.layer addAnimation:animation forKey:@"transform.scale"];
    }
    
    transform.scale.gif
    4.自身大小相关
    • bounds.origin.x | bounds.origin.y
      • 动画view图层上图层和view上的其他控件 不动画view本身
      • 方向x 右为负 左为正
      • 方向y 上为负 下为正
    //bounds.origin.x
    //animation1.fromValue = @0; 默认自身位置为开始位置 为0
    animation1.toValue = @(-self.img.bounds.size.width * 0.5);
    
    //bounds.origin.y
    //animation2.fromValue = @0; 默认自身位置为开始位置 为0
    animation2.toValue = @(-self.img.bounds.size.height * 0.5);
    
    bounds.origin.x/y.gif
    • bounds.size.width | bounds.size.height
      • 动画view本身 不动画view上的其他控件
    //bounds.size.width
    //animation1.fromValue = @(self.img.bounds.size.width); 默认起始值为自身宽度
    animation1.toValue = @(self.img.bounds.size.width * 0.5);
    
    //bounds.size.height
    //animation2.fromValue = @(self.img.bounds.size.height); 默认起始值为自身高度
    animation2.toValue = @(self.img.bounds.size.height * 0.5);
    
    bounds.size.width:height.gif
    5.边角动画
    • cornerRadius | borderWidth | borderColor
    //cornerRadius
    //animation1.fromValue = @(self.img.layer.cornerRadius); 默认自身圆角为起始值
    animation1.toValue = @50;
    animation1.repeatCount = 3;
    animation1.repeatDuration = animation.duration * animation.repeatCount;
    
    //borderWidth
    //animation.fromValue = @(self.img.layer.borderWidth); 默认自身边框宽度为起始值
    animation2.toValue = @20;
    animation2.repeatCount = 3;
    animation2.repeatDuration = animation.duration * animation.repeatCount;
    
    //borderColor
    //animation3.toValue = (__bridge id _Nullable)([self.img.layer borderColor]); //默认自身边框颜色为起始值
    animation3.toValue = (__bridge id _Nullable)([[UIColor cyanColor] CGColor]);
    animation3.repeatCount = 3;
    animation3.repeatDuration = animation.duration * animation.repeatCount;
    
    cornerRadius/borderWidth/borderColor.gif
    6.自身的一些属性
    • opacity 不透明
    animation.fromValue = @1;
    animation.toValue = @0;
    
    opacity.gif
    • backgroundColor
    animation.toValue = (__bridge id _Nullable)([[UIColor yellowColor] CGColor]);
    
    backgroundColor.gif
    • contents (layer.contents)
    animation.fromValue = (__bridge id _Nullable)([[UIImage imageNamed:@"春雨医生"] CGImage]);
    animation.toValue = (__bridge id _Nullable)([[UIImage imageNamed:@"丁香医生"] CGImage]);
    
    contents.gif
    7.阴影相关动画
    • shadowOffset 阴影位置偏移
    //self.img.layer.shadowOpacity = 0.5;
    //self.img.layer.shadowOffset = CGSizeMake(0, 0);
    animation.fromValue = [NSValue valueWithCGSize:CGSizeMake(0, 0)];
    animation.toValue = [NSValue valueWithCGSize:CGSizeMake(20, 20)];
    animation.duration = 3;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    
    shadowOffset.gif
    • shadowColor 阴影颜色
    //self.img.layer.shadowOpacity = 0.5;
    //self.img.layer.shadowOffset = CGSizeMake(20, 20);
    animation.toValue = (__bridge id _Nullable)([UIColor redColor].CGColor);
    
    shadowColor.gif
    • shadowOpacity 阴影不透明值 01(透明完全不透明)
    //self.img.layer.shadowOpacity = 0;
    //self.img.layer.shadowOffset = CGSizeMake(20, 20);
    animation.fromValue = @0;
    animation.toValue = @1;
    
    shadowOpacity.gif
    • shadowRadius 暂且叫做阴影模糊度吧
    //self.img.layer.shadowOpacity = 0.5;
    //self.img.layer.shadowOffset = CGSizeMake(20, 20);
    animation.fromValue = @3;
    animation.toValue = @10;
    
    shadowRadius.gif
    CAKeyframeAnimation

    可以看做是一个有更多位置设定的CABaseAnimation,可以设定keyPath起点、中间关键点(可以是多个)、终点的值,每一帧所对应的时间,动画会沿着设定点进行移动

    CAKeyframeAnimation的一些独有属性

    • values: 关键帧数组对象,里面每一个元素即为一个关键帧
    • path: 动画路径对象,可以指定一个路径,在执行动画时路径会沿着路径移动,注:Path在动画中只会影响视图的Position
    • keyTimes: 设置关键帧对应的时间数组,范围:0.0-1.0之间的浮点型
      • 数组中的每一个连续值都必须大于或等于前面的值,因为里面存储的是动画持续时间内的每一帧的时间点,时间点是从0%-100%,时间不可能回退
      • 为了得到最好的结果,数组中的元素个数应该与values中的元素个数或路径属性中的控制点的数量相匹配
    • timingFunctions: 设置关键帧对应速度效果的数组
    • calculationMode: 这个属性用来设定 关键帧中间的值是怎么被计算的
      NSString * const kCAAnimationLinear
      NSString * const kCAAnimationDiscrete 只展示关键帧的状态,没有中间过程,没有动画
      NSString * const kCAAnimationPaced
      NSString * const kCAAnimationCubic
      NSString * const kCAAnimationCubicPaced
    - (void)animation_CAKeyframeAnimation_Rect
    {
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        animation.duration = 4.0;
        animation.repeatCount = 2;
        animation.repeatDuration = animation.duration * animation.repeatCount;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y)];
        NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x + 150, self.img.center.y)];
        NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x + 150, self.img.center.y + 150)];
        NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y + 150)];
        NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y)];
        animation.values = @[value1, value2, value3, value4, value5];
        animation.keyTimes = @[@0, @0.4, @0.5, @0.9, @1.0];
        
        /*  利用贝塞尔画的一个矩形,跟上面效果一样,只不过不能设置关键帧动画时间
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(self.img.center.x, self.img.center.y, 150, 150)];
        animation.path = path.CGPath;
         */
        
        [self.img.layer addAnimation:animation forKey:@"position"];
    }
    
    values_rect.gif path_rect.gif

    利用贝塞尔画个圆路径动画

    - (void)animation_CAKeyframeAnimation_Circle
    {
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        animation.duration = 3;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.repeatCount = 2;
        animation.repeatDuration = animation.repeatCount * animation.duration;
        
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.img.center.x + 75, self.img.center.y) radius:75 startAngle:M_PI endAngle:3*M_PI clockwise:YES];
      
        animation.path = path.CGPath;
        [self.img.layer addAnimation:animation forKey:@"position"];
    }
    
    path_circle.gif
    CATransition

    转场动画,在开发中巧用会有意想不到的效果,还方便

    /**
     *  CATransition (type) 过渡动画的类型
     *
     *  kCATransitionFade   渐变
     *  kCATransitionMoveIn 覆盖
     *  kCATransitionPush   推出
     *  kCATransitionReveal 揭开(可以说是抽开)
     *
     *  私有动画类型的值有:"cube"、"suckEffect"、"oglFlip"、 "rippleEffect"、"pageCurl"、"pageUnCurl"等等
     */
        
    /**
     *  CATransition (subtype) 过渡动画的方向
     *
     *  kCATransitionFromRight  从右边
     *  kCATransitionFromLeft   从左边
     *  kCATransitionFromTop    从顶部
     *  kCATransitionFromBottom 从底部
     */
    

    下面来组示例,看下效果,生成四个颜色的图片,来个切换动画

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        static int i = 0;
        i = i >= self.imgsArr.count-1 ? 0:i+1;
        self.img.image = [self.imgsArr objectAtIndex:i];
        CATransition *animation = [CATransition animation];
        animation.duration = 0.5;
        animation.type = @"cube";
        animation.subtype = kCATransitionFromRight;
        [self.img.layer addAnimation:animation forKey:@"transition"];
    }
    - (NSArray *)imgsArr
    {
        if (!_imgsArr) {
            _imgsArr = [NSArray arrayWithObjects:[self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor redColor]],
                                                 [self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor greenColor]],
                                                 [self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor cyanColor]],
                                                 [self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor magentaColor]], nil];
        }
        return _imgsArr;
    }
    //根据size和传进来的color生成一张颜色图片
    - (UIImage *)returnImage1WithCGSize:(CGSize)size andColor:(UIColor *)color
    {
        UIGraphicsBeginImageContextWithOptions(size, NO, 0);
        
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, size.width, size.height)];
        [color setFill];
        [path fill];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return image;
    }
    

    我平时开发中的图片轮播一般都是用这个动画做的,加两个手势就行,缺点就是不能翻页中间不能停留,没有scroll那么全的功能,但是代码简单方便


    kCATransitionPush_模仿轮播图.gif
    kCATransitionFade.gif
    kCATransitionMoveIn.gif
    kCATransitionReveal.gif
    私有-cube.gif

    示例: 我最近做项目遇到这样一个问题,登录界面跳到主界面的时候我一般喜欢用根视图去跳,因为我认为登录注册界面可能八百年才用到一次,用导航或者模态来转场不是太好,但是用根视图跳转显得太突兀,动画不太好看,这时转场动画便派上用场

    //登录
    SideslipViewController *sideslip = [[SideslipViewController alloc] init];
    [UIApplication sharedApplication].keyWindow.rootViewController = sideslip;
        
    CATransition *transition = [[CATransition alloc] init];
    transition.duration = 0.3;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    transition.type = kCATransitionReveal;
    transition.subtype = kCATransitionFromTop;
    [[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"LoginIn"];
    
    //登出
    LoginViewController *login = [[LoginViewController alloc] init];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:login];
    nav.navigationBar.hidden = YES;
    [UIApplication sharedApplication].keyWindow.rootViewController = nav;
        
    CATransition *transition = [[CATransition alloc] init];
    transition.duration = 0.3;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromBottom;
    [[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"LoginOut"];
    
    LoginInOrOut_Animation.gif
    CASpringAnimation

    iOS9之后出来的新的弹簧动画,继承CABasicAnimation

    /** 一些重要的属性
     *  CASpringAnimation的重要属性:
     *
     *  mass:质量(影响弹簧的惯性,质量越大,弹簧惯性越大,运动的幅度越大)
     *  stiffness:弹性系数(弹性系数越大,弹簧的运动越快)
     *  damping:阻尼系数(阻尼系数越大,弹簧的停止越快)
     *  initialVelocity:初始速率(弹簧动画的初始速度大小,弹簧运动的初始方向与初始速率的正负一致,若初始速率为0,表示忽略该属性)
     *  settlingDuration:结算时间(根据动画参数估算弹簧开始运动到停止的时间,动画设置的时间最好根据此时间来设置)
     */
    
    CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"position"];
    animation.mass = 10.0;
    animation.stiffness = 500;
    animation.damping = 10;
    animation.initialVelocity = 5.0f;
    animation.duration = animation.settlingDuration; //时间需要注意下,用系统计算出来动画所需时间
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y + 200)];
    [self.img.layer addAnimation:animation forKey:@"position"];
    
    CASpringAnimation_position.gif
    以上零散单个Demo地址: https://github.com/SupermanChao/Animation
    CAAnimationGroup

    使用Group可以将多个动画合并一起加入到图层中,Group中所有动画一起执行,可以展示很多动画种类
    一个简单的动画组例子,选取颜色
    Demo地址:https://github.com/SupermanChao/AnimationGroup

    Animation-Group.gif
    CATransaction

    最后讲一下事务(CATransaction),在核心动画里面存在事务(CATransaction)这样一个概念,它负责协调多个动画原子更新显示操作,简单来说事务是核心动画里面的一个基本的单元,动画的产生必然伴随着layer的Animatable属性的变化,而layer属性的变化必须属于某一个事务

    事务分为隐式和显式:

    • 隐式:没有明显调用事务的方法,由系统自动生成事务,比如直接设置一个layer的position属性,则会在当前线程自动生成一个事务,并在下一个runLoop中自动commit事务
    • 显式:明显调用事务的方法([CATransaction begin]和[CATransaction commit])
    事务的可设置属性(会覆盖隐式动画的设置):
    //动画持续时间
    + (CFTimeInterval)animationDuration;
    //动画时间曲线
    + (nullable CAMediaTimingFunction *)animationTimingFunction;
    //是否关闭动画
    + (BOOL)disableActions;
    //动画执行完毕回调
    + (nullable void (^)(void))completionBlock;
    

    下面来个小例子,圆环进度动画,先看效果后上代码
    代码地址:https://github.com/SupermanChao/CATransaction-Demo

    LCProgressAnimation.gif
    //圆环贝塞尔曲线
    - (UIBezierPath *)path
    {
        if (!_path) {
            CGFloat radius = (MIN(self.frame.size.width, self.frame.size.height) - self.lineWidth) * 0.5;
            CGPoint center = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5);
            _path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI + M_PI_2 clockwise:YES];
        }
        return _path;
    }
    
    //轨道
    - (CAShapeLayer *)outLayer
    {
        if (!_outLayer) {
            _outLayer = [CAShapeLayer layer];
            _outLayer.lineWidth = self.lineWidth;
            _outLayer.fillColor = [UIColor clearColor].CGColor;
            _outLayer.strokeColor = self.pathwayColor.CGColor;
            _outLayer.path = self.path.CGPath;
        }
        return _outLayer;
    }
    
    //进度
    - (CAShapeLayer *)progressLayer
    {
        if (!_progressLayer) {
            _progressLayer = [CAShapeLayer layer];
            _progressLayer.lineWidth = self.lineWidth;
            _progressLayer.fillColor = [UIColor clearColor].CGColor;
            _progressLayer.lineCap = kCALineCapRound;
            _progressLayer.strokeColor = self.scheduleColor.CGColor;
            _progressLayer.path = self.path.CGPath;
            _progressLayer.strokeStart = 0;
            _progressLayer.strokeEnd = 0.001;
        }
        return _progressLayer;
    }
    
    //定时器协调运作
    - (void)onTimer
    {
        switch (self.accuracy) {
            case LCAnimationAccuracyLow:
                self.progress += 2;
                break;
            case LCAnimationAccuracyHeight:
                self.progress += 0.5;
                break;
            case LCAnimationAccuracyVeryHeight:
                self.progress += 0.1;
                break;
            default:
                self.progress += 1;
                break;
        }
        
        [CATransaction begin];
        [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
        [CATransaction setAnimationDuration:self.totalTime / self.count];
        self.progressLayer.strokeEnd = self.progress / 100.0;
        [CATransaction commit];
        
        if (self.progress >= 99.99) {
            [self lc_stopAnimation];
            if ([self.delegate respondsToSelector:@selector(lc_animationFinishAction)]) {
                [self.delegate lc_animationFinishAction];
            }
        };
    }
    

    相关文章

      网友评论

          本文标题:iOS开发-核心动画(CAAnimation)相关

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