Cowboy Tech

CAShapeLayer的使用

CAShapeLayer简介

  1. CAShapeLayer继承至CALayer,可以使用CALayer的所有属性值
  2. CAShapeLayer需要与贝塞尔曲线配合使用才有意义
  3. 使用CAShapeLayer与贝塞尔曲线可以实现不在view的drawRect方法中画出一些想要的图形
  4. CAShapeLayer属于CoreAnimation框架,其动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用情况

代码

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) NSTimer      *timer;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// 创建shapeLayer
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.frame         = (CGRect){CGPointMake(0, 0), CGSizeMake(200, 200)};
_shapeLayer.position      = self.view.center;
_shapeLayer.path          = [self getStar1BezierPath].CGPath;
_shapeLayer.fillColor     = [UIColor clearColor].CGColor;
_shapeLayer.strokeColor   = [UIColor redColor].CGColor;
_shapeLayer.lineWidth     = 2.f;
[self.view.layer addSublayer:_shapeLayer];

// 创建定时器
_timer = [NSTimer scheduledTimerWithTimeInterval:1.f
                                          target:self
                                        selector:@selector(pathAnimation)
                                        userInfo:nil
                                         repeats:YES];
}

/**
 *  执行path的动画
 */
- (void)pathAnimation {
static int i = 0;
if (i++ % 2 == 0) {
    CABasicAnimation *circleAnim = [CABasicAnimation animationWithKeyPath:@"path"];
    circleAnim.removedOnCompletion = NO;
    circleAnim.duration            = 1;
    circleAnim.fromValue           = (__bridge id)[self getStar1BezierPath].CGPath;
    circleAnim.toValue             = (__bridge id)[self getStar2BezierPath].CGPath;
    _shapeLayer.path               = [self getStar2BezierPath].CGPath;
    [_shapeLayer addAnimation:circleAnim forKey:@"animateCirclePath"];
} else {
    CABasicAnimation *circleAnim = [CABasicAnimation animationWithKeyPath:@"path"];
    circleAnim.removedOnCompletion = NO;
    circleAnim.duration            = 1;
    circleAnim.fromValue           = (__bridge id)[self getStar2BezierPath].CGPath;
    circleAnim.toValue             = (__bridge id)[self getStar1BezierPath].CGPath;
    _shapeLayer.path               = [self getStar1BezierPath].CGPath;
    [_shapeLayer addAnimation:circleAnim forKey:@"animateCirclePath"];
}
}

/**
 *  贝塞尔曲线1
 *
 *  @return 贝塞尔曲线
 */
-(UIBezierPath *)getStar1BezierPath {
//// Star Drawing
UIBezierPath* starPath = [UIBezierPath bezierPath];
[starPath moveToPoint: CGPointMake(22.5, 2.5)];
[starPath addLineToPoint: CGPointMake(28.32, 14.49)];
[starPath addLineToPoint: CGPointMake(41.52, 16.32)];
[starPath addLineToPoint: CGPointMake(31.92, 25.56)];
[starPath addLineToPoint: CGPointMake(34.26, 38.68)];
[starPath addLineToPoint: CGPointMake(22.5, 32.4)];
[starPath addLineToPoint: CGPointMake(10.74, 38.68)];
[starPath addLineToPoint: CGPointMake(13.08, 25.56)];
[starPath addLineToPoint: CGPointMake(3.48, 16.32)];
[starPath addLineToPoint: CGPointMake(16.68, 14.49)];
[starPath closePath];

return starPath;
}

/**
 *  贝塞尔曲线2
 *
 *  @return 贝塞尔曲线
 */
-(UIBezierPath *)getStar2BezierPath {
//// Star Drawing
UIBezierPath* starPath = [UIBezierPath bezierPath];
[starPath moveToPoint: CGPointMake(22.5, 2.5)];
[starPath addLineToPoint: CGPointMake(32.15, 9.21)];
[starPath addLineToPoint: CGPointMake(41.52, 16.32)];
[starPath addLineToPoint: CGPointMake(38.12, 27.57)];
[starPath addLineToPoint: CGPointMake(34.26, 38.68)];
[starPath addLineToPoint: CGPointMake(22.5, 38.92)];
[starPath addLineToPoint: CGPointMake(10.74, 38.68)];
[starPath addLineToPoint: CGPointMake(6.88, 27.57)];
[starPath addLineToPoint: CGPointMake(3.48, 16.32)];
[starPath addLineToPoint: CGPointMake(12.85, 9.21)];
[starPath closePath];

return starPath;
}
@end

贝塞尔曲线与CAShapeLayer的关系

  1. CAShapeLayer中有Shape这个单词,顾名思义,它需要一个形状才能生效
  2. 贝塞尔曲线可以创建基于矢量的路径
  3. 贝塞尔曲线给CAShapeLayer提供路径,CAShapeLayer在提供的路径中进行渲染,路径会闭环,所以路径绘制出了Shape
  4. 用于CAShapeLayer的贝塞尔曲线作为path,其path是一个首尾相接的闭环的曲线,即使该贝塞尔曲线不是一个闭环的曲线

代码

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 创建椭圆形贝塞尔曲线
UIBezierPath *oval = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 200, 100)];

// 创建矩形贝塞尔曲线
UIBezierPath *rect = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 200, 100)];

// 创建圆形贝塞尔曲线
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 100)];

// 创建CAShapeLayer
CAShapeLayer *shape = [CAShapeLayer layer];
shape.frame         = CGRectMake(0, 0, 200, 50);
shape.position      = self.view.center;

// 显示CAShapeLayer的边界
shape.borderWidth   = 1.f;

// 禁止内容显示超出CAShapeLayer的frame值
shape.masksToBounds = YES;

// 修改贝塞尔曲线的填充颜色
shape.fillColor     = [UIColor redColor].CGColor;

// 建立贝塞尔曲线与CAShapeLayer之间的关联
shape.path = circle.CGPath;

// 添加并显示
[self.view.layer addSublayer:shape];
}

StrokeStart与StrokeEnd动画

进度条效果 - 利用GPU实现,不占内存

  1. 将ShapeLayer的fillColor设置成透明背景
  2. 设置线条的宽度(lineWidth)的值
  3. 设置线条的颜色
  4. 将strokeStart值设定成0,然后让strokeEnd的值变化触发隐式动画
  5. strokeStart的值一定小于strokeEnd

代码

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) NSTimer      *timer;      // 定时器
@property (nonatomic, strong) CAShapeLayer *shapeLayer; // 形状layer

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

// 设置背景色
self.view.backgroundColor = [UIColor colorWithRed:0.878 green:0.878 blue:0.878 alpha:1];

// 创建椭圆形贝塞尔曲线
UIBezierPath *oval        = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 100)];

// 创建CAShapeLayer
_shapeLayer               = [CAShapeLayer layer];
_shapeLayer.frame         = CGRectMake(0, 0, 100, 100);
_shapeLayer.position      = self.view.center;

// 修改CAShapeLayer的线条相关值
_shapeLayer.fillColor     = [UIColor clearColor].CGColor;
_shapeLayer.strokeColor   = [UIColor redColor].CGColor;
_shapeLayer.lineWidth     = 2.f;
_shapeLayer.strokeStart   = 0.f;
_shapeLayer.strokeEnd     = 0.f;

// 建立贝塞尔曲线与CAShapeLayer之间的关联
_shapeLayer.path          = oval.CGPath;

// 添加并显示
[self.view.layer addSublayer:_shapeLayer];

// 创建定时器
_timer = [NSTimer scheduledTimerWithTimeInterval:1.f
                                          target:self
                                        selector:@selector(animationEventTypeTwo)
                                        userInfo:nil
                                         repeats:YES];
                                     }

/**
 *  动画效果1
*/
- (void)animationEventTypeOne {
// 执行隐式动画
_shapeLayer.strokeEnd = arc4random() % 100 / 100.f;
}

/**
 *  动画效果2
 */
 - (void)animationEventTypeTwo {
CGFloat valueOne = arc4random() % 100 / 100.f;
CGFloat valueTwo = arc4random() % 100 / 100.f;

// 执行隐式动画
_shapeLayer.strokeStart = valueOne < valueTwo ? valueOne : valueTwo;
_shapeLayer.strokeEnd   = valueOne > valueTwo ? valueOne : valueTwo;
}

用CAShapeLayer实现圆形进度条效果

  1. 确定需要设定的参数
  2. 实现细节
  3. 进行测试

代码

viewController.m

#import "ViewController.h"
#import "CircleView.h"
@interface ViewController ()
{
CircleView *circle;
}
@end

@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];

circle             = [[CircleView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
circle.center      = self.view.center;
circle.startValue  = 0.5;
circle.lineWidth   = 3.f;
circle.lineColor   = [UIColor grayColor];
[self.view addSubview:circle];

[self performSelector:@selector(delayAnimation)
           withObject:nil
           afterDelay:3.f];
       }

- (void)delayAnimation {
circle.value = 1.f;
}

circleView.h

#import <UIKit/UIKit.h>

@interface CircleView : UIView
@property (nonatomic, assign) CGFloat  startValue; // 起始值(0~1)
@property (nonatomic, assign) CGFloat  lineWidth;  // 线宽(>0)
@property (nonatomic, strong) UIColor *lineColor;  // 线条颜色
@property (nonatomic, assign) CGFloat  value;      // 变化的值

@end

circleView.m

#import "CircleView.h"
@interface CircleView ()

@property (nonatomic, strong) CAShapeLayer *shapeLayer;

@end
@implementation CircleView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
    // 创建出CAShapeLayer
    _shapeLayer       = [CAShapeLayer layer];
    _shapeLayer.frame = self.bounds;

    // 创建出贝塞尔曲线
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:self.bounds];

    // 贝塞尔曲线与CAShapeLayer产生关联
    _shapeLayer.path = path.CGPath;

    // 基本配置
    _shapeLayer.fillColor   = [UIColor clearColor].CGColor;
    _shapeLayer.lineWidth   = 1.f;
    _shapeLayer.strokeColor = [UIColor redColor].CGColor;
    _shapeLayer.strokeEnd   = 0.f;

    // 添加到当前view
    [self.layer addSublayer:_shapeLayer];
}
return self;
}

@synthesize startValue = _startValue;
- (void)setStartValue:(CGFloat)startValue {
_startValue           = startValue;
_shapeLayer.strokeEnd = startValue;
}
- (CGFloat)startValue {
return _startValue;
}

@synthesize lineWidth = _lineWidth;
- (void)setLineWidth:(CGFloat)lineWidth {
_lineWidth            = lineWidth;
_shapeLayer.lineWidth = lineWidth;
}
- (CGFloat)lineWidth {
return _lineWidth;
}

@synthesize lineColor = _lineColor;
- (void)setLineColor:(UIColor *)lineColor {
_lineColor              = lineColor;
_shapeLayer.strokeColor = lineColor.CGColor;
}
- (UIColor *)lineColor {
return _lineColor;
}

@synthesize value = _value;
- (void)setValue:(CGFloat)value {
_value                = value;
_shapeLayer.strokeEnd = value;
}
- (CGFloat)value {
return _value;
}


@end