美文网首页
iOS高级动画(二)

iOS高级动画(二)

作者: 找不到工作的iOS | 来源:发表于2017-09-07 17:54 被阅读264次

    1. 贝塞尔曲线

    1.1 贝塞尔曲线反转

    • 如果不在 CAShaperLayer 里操作,那么就只能在 UIView 的 -drawRect 下操作才会显示
    • 反转 : 起始点和终点的位置对调
    - (void)drawRect:(CGRect)rect {
        
       //反转  起始点和终点的反转
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(20.f, 100.f)];
        [path addLineToPoint:CGPointMake(250.f, 100.f)];
        
        //注意这里提前生成了反转的路径,再回到原路径操作
        UIBezierPath *path1 = [path bezierPathByReversingPath];
        
        //这里是path 不是path1
        [path addLineToPoint:CGPointMake(300.f, 200.f)];
        path.lineWidth = 3.f;
        [[UIColor redColor] set];
        [path stroke];
        
        //向下平移200,防止重合看不出效果
        CGAffineTransform tranform = CGAffineTransformMakeTranslation(0.f, 200.f);
        [path1 applyTransform:tranform];
        [path1 addLineToPoint:CGPointMake(300.f, 200.f)];
        
        path1.lineWidth = 3.f;
        [[UIColor redColor] set];
        [path1 stroke];
    }
    
    反转.png

    1.2 多重贝塞尔路径

    • 这里不能用-add(比如内部画圆的情况),因为-add会从绘制完矩形的终点,再描一条线到圆形绘制的起点,实际上是一条路径,而现在我们需要的效果是由两条路径组成

    • 使用-add方法(多了一条线)
      [path addArcWithCenter:CGPointMake(150.f, 150.f) radius:30 startAngle:0 endAngle:M_PI*2 clockwise:YES];

      morepath.png
    • 使用-appendPath方法 (双路径)

    path2.png
    • 如果我们单纯的填充颜色,情况如下(全填充):
    fill.png
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100.f, 100.f, 100.f, 100.f) cornerRadius:1];
    
    [path appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(150.f, 150.f) radius:30 startAngle:0 endAngle:M_PI clockwise:YES] ]; 
    
    CAShapeLayer *layer1 = [CAShapeLayer layer];
       layer1.path = path.CGPath;
       layer1.strokeColor = [UIColor lightGrayColor].CGColor;
       layer1.fillColor = [UIColor redColor].CGColor;
       
       [self.layer addSublayer:layer1];
    
    • 如果需要局部填充,将内部圆路径反转:
    fill2.png
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100.f, 100.f, 100.f, 100.f) cornerRadius:1];
    
    //path2 是反转的
    [path appendPath:[[UIBezierPath bezierPathWithArcCenter:CGPointMake(150.f, 150.f) radius:30 startAngle:0 endAngle:M_PI clockwise:YES] bezierPathByReversingPath]]; 
    
    CAShapeLayer *layer1 = [CAShapeLayer layer];
       layer1.path = path.CGPath;
       layer1.strokeColor = [UIColor lightGrayColor].CGColor;
       layer1.fillColor = [UIColor redColor].CGColor;
       
       [self.layer addSublayer:layer1];
    

    1.3 贝塞尔虚线

        for (int i=0; i<20.f; i++) {
           
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path moveToPoint:CGPointMake(100.f, 100.f+20*i)];
            [path addLineToPoint:CGPointMake(250.f, 100.f+20*i)];
            path.lineWidth = 3.f;
            [[UIColor redColor] set];
            
            CGFloat arr[] = {8.f, 4.f, 16.f, 8.f};
            [path setLineDash:arr count:4 phase:i];
            
            [path stroke];
            
        }
    
    - count 这条虚线有几组
    - phase 虚线起始位置样式
    - arr 表示每小组虚线里面每一小段的长度(8.f红色 4.f白色 16.f红色 8.f白色)
    
    xvline.png

    2. BaseAnimation

    2.1 基本属性介绍

    • 关于动画过程的实时监听
       - 一般情况下我们无法监听动画中实时变化的值,如frame。所以我们需要了解下面的机制
       - layer 的动画由 model层 与 presentationlayer 组成,model层即是layer原始的frame等属性
       - 动画的过程中,对于layer而言:model层不变,改变的只是presentationlayer的值(只有在动画开始的时候才有的)
    
    • FillMode
    /*
    * 选择bothMode,那么动画会从直接从fromValue的位置开始
    * 选择forwardsMode,那么动画会从初始frame移动到fromValue的位置 ,再从fromValue移动到toValue
    */
    
    • CACurrentMediaTime
    一个专门服务于CA的绝对时间,可用于解决多个动画协同性的问题
    
    • 一个简单的 BaseAnimation 作为讲解属性的示例:
    layer = [EOCShaperLayer layer];
        layer.frame = CGRectMake(50.f, 50.f, 100.f, 100.f);
        layer.delegate = self;
        layer.backgroundColor = [UIColor lightGrayColor].CGColor;
        [self.view.layer addSublayer:layer];
    
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"newPosition"];
        animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(300.f, 300.f)];
        animation.beginTime = CACurrentMediaTime() + 1.f; //一个专门服务于CA的绝对时间,可用于解决多个动画的同步性与相对时间的问题
        animation.toValue = [NSValue valueWithCGPoint:CGPointMake(200.f, 200.f)];
    //    layer.position = CGPointMake(200.f, 200.f);
        animation.removedOnCompletion = NO; 
    
    
    /*
    * 选择bothMode,那么动画会从直接从fromValue的位置开始
    * 选择forwardsMode,那么动画会从初始frame移动到fromValue的位置 ,再从fromValue移动到toValue
    */
        animation.fillMode = kCAFillModeBoth;  
        
        NSLog(@"%@", layer.presentationLayer);
        
        animation.duration = 3.f;
        [layer addAnimation:animation forKey:nil];
    

    2.2 监听动画的实时属性变化

    • 上面的代码为何是 newPosition
     - 经实践发现,我们监听系统的position并不会实时显示坐标
     - 所以我们在layer层自定义了一个 newPosition 的属性
    
    @interface EOCShaperLayer : CAShapeLayer
    
    @property(nonatomic, assign)CGPoint newPosition;
    
    @end
    
    /*
    * 系统的某个key值变化是否需要调用 display 方法
    */
    + (BOOL)needsDisplayForKey:(NSString *)key {
        
        if ([key isEqualToString:@"newPosition"]) {
            
            return YES;
            
        }
        
        return [super needsDisplayForKey:key];
        
    }
    
    /*
     * display 1秒调用60次 (fps = 60)
    */
    - (void)display {
        
        // self的属性 就是 presentationLayer 的属性
        // presentationLayer 本质就是动画时生成的 layer(self)
        self.position = self.presentationLayer.newPosition;
    
        // 由于系统内部做了处理,我们打印 position 是不会显示值的
        NSLog(@"newPosition%@", NSStringFromCGPoint(self.presentationLayer.newPosition));
        
    }
    
    @end
    

    2.3 subLayer 需要手动释放的特例

    • 在 Controller 生命周期结束的时候(-dealloc),有两种情况下 subLayer 需要手动释放
    • 设置了 subLayer 的 delegate
    • 使用了 layer 的 display 方法

    3. 关键帧动画

    • 多阶段贝塞尔曲线
    ![Uploading 二次贝塞尔曲线_707633.gif . . .] 二次贝塞尔曲线.gif 三次贝塞尔曲线.gif
    • 一个飞机沿着路径运动的动画
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(40.f, 175.f)];
        [path addCurveToPoint:CGPointMake(300.f, 175.f) controlPoint1:CGPointMake(50.f, 40.f) controlPoint2:CGPointMake(200.f, 300.f)];
        pathLayer.path = path.CGPath;
        pathLayer.lineWidth = 2.f;
        pathLayer.fillColor = [UIColor clearColor].CGColor;
        [pathLayer setStrokeColor:[UIColor redColor].CGColor];
        [self.view.layer addSublayer:pathLayer];
        
        
        shapeLayer = [CAShapeLayer layer];
        shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
        shapeLayer.bounds = CGRectMake(0.f, 0.f, 50.f, 50.f);
        shapeLayer.position = CGPointMake(40.f, 175.f);
        [self.view.layer addSublayer:shapeLayer];
    
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    [shapeLayer addAnimation:anim forKey:@"eoc"];
    
    plan1.png plan2.png
    • 在这里有一个缺陷,飞机的机头一直没有方向上的变化
    • 飞行优化方式
    anim.rotationMode = kCAAnimationRotateAuto;
    
    plan.png
    • 关键帧动画 anim.values
    - 会偏离出路径,移动到设置的关键帧上
    - 如果不设置 keyTimes 那么动画轨迹都是平滑的
    
    anim.values = @[[NSValue valueWithCGPoint:CGPointMake(100, 100)], [NSValue valueWithCGPoint:CGPointMake(200, 300)], [NSValue valueWithCGPoint:CGPointMake(50, 500)]];
    
    anim.keyTimes = @[@0, @0.75, @1];  //keyTimes可以不设置,每个元素的时间点对应每个关键帧,每一个值都是要小于1
    
    • anim.calculationMode
    - <动画匀速播放模式>,如果在keyTimes设置的时间比较突兀,那么在设置了Paced后可以缓冲这种影响
    anim.calculationMode =  kCAAnimationCubicPaced; 
    
    
    • anim.autoreverses
      anim.autoreverses = YES 自动反转动画,类似倒带

    • anim.beginTime

    - 必须要加  CACurrentMediaTime() ,这表示的一个系列用于动画层面的一个独立的时间单位
    anim.beginTime = CACurrentMediaTime() + 2.f; // 表示延时两秒开始
    
    • anim.timeOffset
    - 表示从动画整个时间轴中的 偏移到第 1 秒开始播放;
    - 比如动画长5秒,timeOffset = 1.f,实际播放的是后4秒;
    anim.timeOffset = 1.f;
    
    • <CAAnimationDelegate>
    - (void)animationDidStop:(CAKeyframeAnimation *)anim finished:(BOOL)flag {
        /*
         * 这里的key值是add时候赋予的
         * [shapeLayer addAnimation:anim forKey:@"eoc"];
         */    
        NSLog(@"animation %@", [anim valueForKey:@"eoc"]);  
    
        //深拷贝 下面不会执行,因为地址不同
        if ([anim isEqual:[shapeLayer animationForKey:@"eoc"]]) {
                    NSLog(@"111");        
        }
    }
    
    

    4. 组动画

    • 值得注意的是,如果要设置多个动画的相对的进行时间,我们只需要在 animationGroup.beginTime 使用一次 CACurrentMediaTime() 属性,在 anim1 或者 anim2 中的 beginTime 属性里不需要使用 CACurrentMediaTime(),直接赋一个值就好,比如 anim1.beginTime = 2.f;
        CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
        animationGroup.animations = @[anim1, anim2];
        animationGroup.beginTime = CACurrentMediaTime()+1;
        animationGroup.duration = 4.f;
        animationGroup.repeatCount = MAXFLOAT;
        [shapeLayer addAnimation:animationGroup forKey:nil];
    

    5. CAMediaTiming

    5.1 speed属性

    • speed 流速,可以理解为动画的速度
    • 默认值为1.f,如果设置为 0,layer上的动画不会执行;设置为 2,动画 2倍数进行

    5.2 基于 speed = 0;的可控进度的交互动画

    • 通过滑块可以自己控制飞机飞行的位置
        shapeLayer = [CAShapeLayer layer];
        shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
        shapeLayer.frame = CGRectMake(25.f, 75.f, 50.f, 50.f);
        shapeLayer.speed = 0.f; // Laye 的流速影响 anim 的流速
        [self.view.layer addSublayer:shapeLayer];
        
        
        
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
        anim.delegate = self;
        anim.duration = 10.f;
        anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(50.f, 50.f)];
        anim.toValue = [NSValue valueWithCGPoint:CGPointMake(300.f, 300.f)];
        [shapeLayer addAnimation:anim forKey:nil];
    
        
        UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(100.f, 300.f, 200.f, 30.f)];
        [slider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged];
        slider.minimumValue = 0;
        slider.maximumValue = 10.f;
        [self.view addSubview:slider];
    
    - (void)sliderAction:(UISlider *)slider {
        //这里是Layer.timeOffset
        shapeLayer.timeOffset = slider.value;
        
    }
    

    5.3 CA动画的播放与暂停

    • 一个用 touch 实现的播放与暂停
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        if (0 == i) {  //暂停
            
       /*
        * timeoffset 和 时间相关的流逝 没什么太大的关系
        * timeoffset 类型一个进度条, 你在晚上播放和 早上播放 都在在进度条暂停的那个位置
        * 这个主要是把,当前这个时间点,转化为进度条上的某个时间点
        */
          pausedTime = [shapeLayer convertTime:CACurrentMediaTime() fromLayer:nil];
          shapeLayer.speed = 0.f;
          shapeLayer.timeOffset = pausedTime;
            
        } else if (1 == i) {  //开始
               
            shapeLayer.speed = 1.f;
            shapeLayer.beginTime = CACurrentMediaTime();
            
        }
        
        i++;
        i%=2;   
        
    }
    

    6. 转场动画

     并不作用于指定的图层属性(比如 keypath 为 bounds position),这就是说你可以在即使不能准确得 知改变了什么的情况下对图层做动画
     */
    
    //合法的转场动画类型有:
    //fade:默认。faker淡出,layer淡入
    //moveIn:layer移入覆盖faker
    //push:layer推入,faker推出
    //reveal:覆盖在layer上面的faker被移出
    
    //私有:(被苹果ban了,不建议直接使用)
    //cube:立方体旋转,layer将会在呈现的面,faker在不可见的面
    //suckEffect:覆盖在layer上面的faker被抽离
    //oglFlip:将背面的layer翻转到前面,faker翻转到背面、、
    //rippleEffect:伴随着水面波动动画,faker淡出,layer淡入
    //pageCurl:翻到下一页,faker被翻走,呈现layer
    //pageUnCurl:翻回上一页,layer被翻回并覆盖faker
    //cameraIrisHollowOpen:下面这两个是特殊的。镜头开,同时呈现部分为透明,而不是layer
    //cameraIrisHollowClose:类似上面,镜头关
    
    //subtype
    
    //4个子类型,表示上左下右4个转场动画方向:
    //fromTop
    //fromLeft
    //fromBottom
    //fromRight
    
    • 示例动画代码
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
        
        shapeLayer = [CAShapeLayer layer];
        shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
        shapeLayer.frame = CGRectMake(25.f, 75.f, 50.f, 50.f);
        [self.view.layer addSublayer:shapeLayer];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        
        CATransition *anim = [CATransition animation];
        anim.type = @"cube";
        anim.subtype = kCATransitionFromRight;
        
        anim.duration = 4.f;
        [shapeLayer addAnimation:anim forKey:nil];
        
        shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"bg-mine.png"].CGImage;
    
        
    }
    
    • 效果
    animation.png

    相关文章

      网友评论

          本文标题:iOS高级动画(二)

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