iOS 学习历程动画积累CAShapeLayer + UIBezierPath

CAShapeLayer 初探

2018-04-06  本文已影响56人  人话博客

CAShapeLayer 是什么?
CAShapeLayer 是一个继承自 CALayer 的一个子Layer。

image.png

为什么有了 CALayer 之后,还要有一个 CAShapeLayer ? 有什么优势吗?
我们都知道 CALayer,是 UIView 内部一个用于显示的容器。
那 CAShapeLayer 作为另外的一种 Layer,出现的目的是什么的?

个人理解:CAShapeLayer,作为一个 CALayer 它隔离了和 UIView 的强联系。
可以单独的拿过来用。

那仅仅只有这么一点,我们也可以自定义 CALayer 出来用啊。

CAShapeLayer 有一个特别好用的属性,path。我们可以里用 UIBezierPath & CAShapeLayer.path 属性,画出我们任意希望显示的图形。并且搭配 CAShapeLayer 的 strokeBegin & strokeEnd 属性。可以做出一些比较炫酷的图形动画效果。

image.png

CAShapeLayer 的基本使用。

由于 CAShapeLayer 是继承自 CALayer 的,那么 CALayer 可以咋用,CAShapeLayer 就可以咋用。

/**
 1. CAShaperLayer : CALayer.
 2. CALayer 是可以做动画的,所以 CAShapeLayer 也可以做动画。
 3. CAShapeLayer 是 CALayer ,不是 UIView 所以,不能接受事件。
 */
- (void)cashapeLayer {
    
    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
    shapeLayer.frame = CGRectMake(0, 64, 200, 200);
    // 和背景色不冲突?
    shapeLayer.backgroundColor = [UIColor orangeColor].CGColor;
    
    // 设置描边颜色
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;
    // 设置填充颜色
    shapeLayer.fillColor = [UIColor greenColor].CGColor;
    // 发现,这两个属性并没什么乱用。
    // 类似于描边、填充等属性,必须作用在路径上。
    
    [self.view.layer addSublayer:shapeLayer];
}

特点注意:虽然 CAShapeLayer 可以使用 CALayer 的那套。但是,如果不搭配 UIBezierPath的话,那么对于大多数 CAShapeLayer 特有的属性来说,都是不起作用的。


CAShapeLayer 搭配 UIBezierPath 来使用。

CAShapeLayer 搭配 UIBezierPath 使用,才是最适合的做法。
我们可以使用 UIBezierPath 绘制出自己需要的图形,并设置到 CAShapeLayer 上,来达到绘制 CAShapeLayer 的目的。

/**
 使用路径绘制 CAShape
 因为用到了 贝塞尔曲线,所以就可以使用核心绘图的一些参数。
 比如,线宽、描边、填充等。
 */
- (void)shapeLayerUserPath {
    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
    // 创建路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(10, 80)];
    [path addLineToPoint:CGPointMake(100, 80)];
    [path addLineToPoint:CGPointMake(100, 180)];
    [path addLineToPoint:CGPointMake(10, 180)];
    [path addLineToPoint:CGPointMake(10, 80)];
    // [path closePath]; // 闭合路径
    // 设置 CAShapeLayer 的绘制路径
    shapeLayer.path = path.CGPath;
    
    // 有路径了,就可以设置填充颜色,线的样式,描边颜色等。
    shapeLayer.strokeColor = [UIColor purpleColor].CGColor;
    // CAShapeLayer 如果是闭合路径,那么默认的填充颜色是黑色。
    // shapeLayer.fillColor = [UIColor orangeColor].CGColor;
    shapeLayer.fillColor = [UIColor whiteColor].CGColor;
    shapeLayer.lineWidth = 10; // 线宽
    shapeLayer.lineJoin = @"round"; // 线头样式
    shapeLayer.lineCap = @"round"; // 折现结合处样式
    
    [self.view.layer addSublayer:shapeLayer];
}

运行效果:

image.png

当然,UIBezierPath 能够画出什么图形,那么 CAShapeLayer 就能显示出多少中图形。


+ (instancetype)bezierPath;
+ (instancetype)bezierPathWithRect:(CGRect)rect;
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;

- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

// Returns an immutable CGPathRef which is only valid until the UIBezierPath is further mutated.
// Setting the path will create an immutable copy of the provided CGPathRef, so any further mutations on a provided CGMutablePathRef will be ignored.
@property(nonatomic) CGPathRef CGPath;
- (CGPathRef)CGPath NS_RETURNS_INNER_POINTER CF_RETURNS_NOT_RETAINED;

// Path construction

- (void)moveToPoint:(CGPoint)point;
- (void)addLineToPoint:(CGPoint)point;
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);
- (void)closePath;

一个比较常用使用的场景。

image.png

ofo 小黄车的 App 界面,界面的下半部分是一个曲线加一个矩形的样式。

可以利用 CAShapeLayer & UIBezierPath 来实现这么一个场景。

主要思路:在屏幕中间的地方,画一条曲线即可。


/*  |-------------------------------------------------------------------|
        |                                                                   |
        |                                                                   |
        |                        控制点(controlPoint)                       |
        |                                                                   |
        |                                                                   |
        | 起点(moveToPoint)                                    终点(toPint)  |
        -------------------------------------------------------------------
     */

- (void)prepareUI {
    
    CAShapeLayer *layer = [CAShapeLayer layer];
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    // 画一条贝塞尔曲线
    [path moveToPoint:CGPointMake(0, [UIScreen mainScreen].bounds.size.height * 0.5 + 100)];
    [path addQuadCurveToPoint:CGPointMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height * 0.5 + 100) controlPoint:CGPointMake(self.view.center.x, self.view.center.y )];
    
    // 设置曲线到 CAShapeLayer 的 path
    layer.path = path.CGPath;
    
    // 实现线的基本属性
    layer.strokeColor = [UIColor purpleColor].CGColor;
    layer.lineWidth = 5;
    layer.lineCap = kCALineCapRound;
    layer.fillColor = [UIColor whiteColor].CGColor;
    
    [self.view.layer addSublayer:layer];
}

运行效果:

image.png

使用 CAShapeLayer 以动画的方式绘制图形。

由于 CAShapeLayer 继承自 CALayer。CALayer 有可以搭配 CAAnimation 使用。
所以可以使用 CAShapeLayer & UIBezierPath & CAAnimation 来产生比较酷炫的动画效果。

主要是搭配 CAShapeLayer 的 strokeStart & strokeEnd 来实现比较炫酷的效果。

最简单的矩形动画

/**
 动画画矩形
 */
- (void)shapeLayerDrawRect {
    CAShapeLayer *layer = [CAShapeLayer layer];
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(10, 200, 100, 100)];
    layer.path = path.CGPath;
    layer.strokeColor = [UIColor orangeColor].CGColor;
    layer.fillColor = [UIColor whiteColor].CGColor;
    layer.lineWidth = 3;
    layer.lineCap = kCALineCapRound;
    
    [self.view.layer addSublayer:layer];
    
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    anim.fromValue = @0;
    anim.toValue = @1;
    anim.repeatCount = MAXFLOAT;
    anim.duration = 3;
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    
    [layer addAnimation:anim forKey:nil];
}

CAShapeLayer矩形动画.gif

基本上,你能画出的路径都能做出这些动画。

CAShapeLayer动画集.gif

可以利用 CAShapeLayer 的动画特性做一些提示类的动画。

由于 CAShapeLayer 不是 UIResponder ,所以,它不能接受事件。
它的作用,就是展示,也仅仅是展示。
可以利用 CAShapeLayer 的路径动画特性,做一些有功能性的动画。

CAShapeLayer 提示类动画.gif

正确对勾动画


/**
 先画一个圈,在画一个对勾.
 */
- (void)successClick {
    //    UIView *view = [[UIView alloc] init];
    //    view.frame = CGRectMake(0, 0, 40, 40);
    //    view.center = self.view.center;
    //    view.backgroundColor = [UIColor blackColor];
    //    [self.view addSubview:view];
    
    CAShapeLayer *successLayer = [CAShapeLayer layer];
    
    //[view.layer addSublayer:successLayer];
    
    UIBezierPath *layerPath = [UIBezierPath bezierPath];
    // 原形 path
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:40 startAngle:-M_PI_2 endAngle:M_PI * 2 - M_PI_2 clockwise:YES];
    
    [layerPath appendPath:path1];
    
    // 对勾 path
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointMake(self.view.center.x - 40, self.view.center.y)];
    // [path2 moveToPoint:CGPointMake(147,334)];
    [path2 addLineToPoint:CGPointMake(self.view.center.x, self.view.center.y + 40)];
    // [path2 addLineToPoint:CGPointMake(186, 372)];
    [path2 addLineToPoint:CGPointMake(self.view.center.x + 40 / 1.4, self.view.center.y - 40 / 1.4)];
    //[path2 addLineToPoint:CGPointMake(218,309)];
    
    [layerPath appendPath:path2];
    
    successLayer.path = layerPath.CGPath;
    
    successLayer.strokeColor = [UIColor orangeColor].CGColor;
    successLayer.lineCap = kCALineCapRound;
    successLayer.lineWidth = 3;
    successLayer.fillColor = [UIColor whiteColor].CGColor;
    
    // [view.layer addSublayer:successLayer];
    
    CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    ani.fromValue = @0;
    ani.toValue = @1;
    ani.repeatCount = 1;
    ani.duration = 2;
    ani.fillMode = kCAFillModeForwards;
    ani.removedOnCompletion = NO;
    
    ani.delegate = self;
    
    [successLayer addAnimation:ani forKey:@"successAni"];
    
    [self.view.layer addSublayer:successLayer];
    
    _successLayer = successLayer;
}

失败的 XX 动画

- (void)failClick {
    CAShapeLayer *failLayer = [CAShapeLayer layer];
    UIBezierPath *failPathTotal = [UIBezierPath bezierPath];
    
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:40 startAngle:-M_PI_2 endAngle:M_PI * 2 - M_PI_2 clockwise:YES];
    
    [failPathTotal appendPath:path1];
    
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointMake(self.view.center.x - 40 / 1.4, self.view.center.y - 40 / 1.4)];
    [path2 addLineToPoint:CGPointMake(self.view.center.x + 40 / 1.4, self.view.center.y + 40 / 1.4)];
    
    [failPathTotal appendPath:path2];
    
    UIBezierPath *path3 = [UIBezierPath bezierPath];
    [path3 moveToPoint:CGPointMake(self.view.center.x + 40 / 1.4, self.view.center.y - 40 / 1.4)];
    [path3 addLineToPoint:CGPointMake(self.view.center.x - 40 / 1.4, self.view.center.y + 40 / 1.4)];
    
    [failPathTotal appendPath:path3];
    
    failLayer.path = failPathTotal.CGPath;
    
    failLayer.strokeColor = [UIColor orangeColor].CGColor;
    failLayer.lineCap = kCALineCapRound;
    failLayer.lineWidth = 3;
    failLayer.fillColor = [UIColor whiteColor].CGColor;
    
    [self.view.layer addSublayer:failLayer];
    
    CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    ani.fromValue = @0;
    ani.toValue = @1;
    ani.repeatCount = 1;
    ani.duration = 2;
    ani.fillMode = kCAFillModeBackwards;
    ani.removedOnCompletion = NO;
    ani.delegate = self;
    [failLayer addAnimation:ani forKey:@"failAni"];
    
    _failLayer = failLayer;   
}

核心则是,首先需要把动画需要的几条路径计算出来。
比如,第一个 git 路径包含两部分。
1.一个原形路径

 // 圆形 path
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:40 startAngle:-M_PI_2 endAngle:M_PI * 2 - M_PI_2 clockwise:YES];

2.一个折线路径

// 对勾 path
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointMake(self.view.center.x - 40, self.view.center.y)];
    // [path2 moveToPoint:CGPointMake(147,334)];
    [path2 addLineToPoint:CGPointMake(self.view.center.x, self.view.center.y + 40)];
    // [path2 addLineToPoint:CGPointMake(186, 372)];
    [path2 addLineToPoint:CGPointMake(self.view.center.x + 40 / 1.4, self.view.center.y - 40 / 1.4)];

然后把这两个路径添加到另外一个 UIBezierPath 中。

UIBezierPath *layerPath = [UIBezierPath bezierPath];
[layerPath appendPath:path1];
[layerPath appendPath:path2];

(对,UIBezierPath不光可以 add 各种形状,还可以 appendPath:)

UIBezierPath 的 appendPath: 方法

然后把 layerPath 设置给 CAShapeLayer 即可。

CAShapeLayer.path = layerPath.CGPath;

最后总结

  1. CAShapeLayer 继承自 CALayer。所以,可以使用 CAAnimation 来做动画。
  2. CAShapeLayer 有一个 path 属性,可以绘制你能想到的所有的路径。
  3. CAShapeLayer 搭配 strokeStart & strokeEnd 两个属性,可以做出比较炫酷的动画。

注意点:CAShapeLayer 搭配 UIBezierPath 时,默认的 fillColor 是黑色的。

CAShapeLayer 主要是搭配 UIBezierPath 加上 CAAnimation 配合 strokeStart & strokeEnd 来做出比较炫酷的动画。

由于时间比较仓促,这片文章只是简单的介绍了CAShapeLayer的基本使用。

上一篇下一篇

猜你喜欢

热点阅读