iOS高级动画(二)

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容