美文网首页
第五篇:CALayer能力之专用图层

第五篇:CALayer能力之专用图层

作者: 意一ineyee | 来源:发表于2018-01-22 17:08 被阅读37次

    目录

    一、CAShapeLayer结合UIBezierPath绘制任意形状的东西

    二、CAGradientLayer颜色渐变

    三、其它layer


    一、CAShapeLayer结合UIBezierPath绘制任意形状的东西

    简单说一下位图(bit map)和矢量图(vector map):

    • 位图是用像素点表现的图像,拉伸的话会失真;
    • 矢量图是用数学表达式表现的图像,拉伸的时候不会失真。

    CAShapeLayer就是一个用矢量图来绘制图形的专用图层,我们只需要给定它的颜色、宽度等属性,然后用UIBezierPath指定随便一个你想要的path,并把这个BezierPath赋值给shapeLayer的path,它就能自动渲染出我们想要的任何图形了。而BezierPath绘制path无论是指定点还是通过数学公式计算出来的点,其本质都是找到一个一个的点,用BezierPath把它们串起来。

    接下来,我们将通过三个例子,来看一下它怎么使用:

    • 1、画个小人
    • 2、画折线图
    • 3、QQ电话那种波浪效果
    1、画个小人
    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
            
        // 创建shapeLayer
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.strokeColor = [UIColor redColor].CGColor;// 路径的颜色
        shapeLayer.fillColor = [UIColor orangeColor].CGColor;// 路径的空白部分的填充色
        shapeLayer.lineWidth = 5;// 路径的宽度
        shapeLayer.lineCap = kCALineCapSquare;// 路径结尾的样子
        shapeLayer.lineJoin = kCALineJoinRound;// 路径结合点的样子
        [self.view.layer addSublayer:shapeLayer];
        
        // 绘制贝塞尔路径
        UIBezierPath *path = [[UIBezierPath alloc] init];
        [path moveToPoint:CGPointMake(125, 100)];
        [path addArcWithCenter:CGPointMake(100, 100) radius:25 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
        [path moveToPoint:CGPointMake(100, 125)];
        [path addLineToPoint:CGPointMake(100, 175)];
        [path addLineToPoint:CGPointMake(125, 200)];
        [path moveToPoint:CGPointMake(100, 175)];
        [path addLineToPoint:CGPointMake(75, 200)];
        [path moveToPoint:CGPointMake(50, 150)];
        [path addLineToPoint:CGPointMake(150, 150)];
        
        // 贝塞尔路径赋值给shapeLayer的path属性
        shapeLayer.path = path.CGPath;
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    效果:


    1.png
    2、绘制折线图
    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    #define kLineChartWidth 300
    #define kLineChartHeight 200
    
    @interface ViewController ()
    
    @property (strong, nonatomic) UIView *lineChartView;
    @property (strong, nonatomic) CAShapeLayer *lineLayer;
    @property (strong, nonatomic) UIBezierPath *bezierPath;
    
    // 点
    @property (copy,   nonatomic) NSArray *xArray;
    @property (copy,   nonatomic) NSArray *yArray;
    
    // 倍率
    @property (assign, nonatomic) CGFloat xBL;
    @property (assign, nonatomic) CGFloat yBL;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self initialize];
        [self layoutUI];
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            [self drawLine];
        });
    }
    
    
    #pragma mark - private method
    
    // 绘制折线
    - (void)drawLine {
        
        [self.lineChartView.layer addSublayer:self.lineLayer];
    }
    
    // 绘制X轴
    - (void)drawXAxis {
        
        CALayer *xAxisLayer = [[CALayer alloc] init];
        xAxisLayer.frame = CGRectMake(0, kLineChartHeight - 3, kLineChartWidth, 3);
        xAxisLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
        [self.lineChartView.layer addSublayer:xAxisLayer];
    }
    
    // 绘制垂直于X轴的线条,假设我们需要平均分成10份
    - (void)drawXAxisLine {
        
        CGFloat unitGap = kLineChartWidth / 10;// X轴单位间隔
        for (int i = 0; i < 11; i ++) {
    
            CALayer *tempLayer = [CALayer layer];
            tempLayer.frame = CGRectMake(unitGap * i, 0, 2, kLineChartHeight);
            tempLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
            [self.lineChartView.layer addSublayer:tempLayer];
        }
    }
    
    
    #pragma mark - layoutUI
    
    - (void)layoutUI {
        
        [self.view addSubview:self.lineChartView];
        
        [self drawXAxis];
        [self drawXAxisLine];
    }
    
    
    #pragma mark - setter, getter
    
    - (UIView *)lineChartView {
        
        if (_lineChartView == nil) {
            
            _lineChartView = [[UIView alloc] init];
            _lineChartView.frame = CGRectMake(50, 250, kLineChartWidth, kLineChartHeight);
            _lineChartView.backgroundColor = [UIColor yellowColor];
        }
        
        return _lineChartView;
    }
    
    - (CAShapeLayer *)lineLayer {
        
        if (!_lineLayer) {
            
            _lineLayer = [CAShapeLayer layer];
            _lineLayer.strokeColor = [UIColor orangeColor].CGColor;
            _lineLayer.fillColor = [UIColor clearColor].CGColor;
            _lineLayer.lineWidth =  5;
            _lineLayer.lineCap = kCALineCapRound;
            _lineLayer.lineJoin = kCALineJoinRound;
            
            // 将贝塞尔路径赋值给shapeLayer的path
            _lineLayer.path = self.bezierPath.CGPath;
            
            // 创建一个动画
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
            animation.fromValue = @(0.0);
            animation.toValue = @(3.0);
            animation.autoreverses = NO;
            animation.duration = 5;
            
            // 给折线 layer 添加动画
            [_lineLayer addAnimation:animation forKey:nil];
            _lineLayer.strokeEnd = 1;
        }
        
        return _lineLayer;
    }
    
    - (UIBezierPath *)bezierPath {
        
        if (_bezierPath == nil) {
            
            _bezierPath = [[UIBezierPath alloc] init];
            
            for (int i = 0; i < self.xArray.count; i ++) {
                
                CGFloat X = [self.xArray[i] floatValue] * self.xBL;
                CGFloat Y = kLineChartHeight - [self.yArray[i] floatValue] * self.yBL;
                
                CGPoint tempPoint = CGPointMake(X, Y);
                
                if (i == 0) {
                    
                    [_bezierPath moveToPoint:tempPoint];// 设置起点
                }
                
                [_bezierPath addLineToPoint:tempPoint];
            }
        }
        
        return _bezierPath;
    }
    
    
    #pragma mark - initialize
    
    - (void)initialize {
        
        // 点
        self.xArray = @[@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
        self.yArray = @[@"3.4", @"2.9", @"1.05", @"0", @"0", @"1.7", @"6.3", @"3.4", @"5.5", @"2.4"];
        
        // X轴和Y轴的倍率(即X轴和Y轴上每一份代表实际多大)
        CGFloat maxX = [[self.xArray valueForKeyPath:@"@max.floatValue"] floatValue];
        self.xBL = kLineChartWidth / maxX;
        CGFloat maxY = [[self.yArray valueForKeyPath:@"@max.floatValue"] floatValue];
        self.yBL = kLineChartHeight / maxY;
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif
    3、绘制类似QQ电话那个波形图
    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    /**
     *  正弦函数往这一摆 : y = Asin(ωx+φ)+h
     
     *  A(振幅):决定峰值,也就是说靠它来决定正弦波的高度
     *  ω(频率):决定周期,最小正周期T=2π/|ω|,也就是说靠它来决定一个屏幕里能显示几个完整的正弦波
     *  φ(初相):决定起始点偏移X轴的位置,也就是说靠它来决定屏幕最左边起始点偏离X轴的距离
     *  h(偏移):决定波形偏移X轴的位置,也就是说靠它来决定整条正弦波偏离X轴的距离
     */
    
    #import "ViewController.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface ViewController ()
    
    @property (strong, nonatomic) UIView *contentView;
    @property (strong, nonatomic) CAShapeLayer *layer1;
    @property (strong, nonatomic) CAShapeLayer *layer2;
    @property (strong, nonatomic) CAShapeLayer *layer3;
    
    @property (strong, nonatomic) CADisplayLink *displayLink;// CADisplayLink和NSTimer差不多,后面会详细介绍到,这里先用着
    @property (assign, nonatomic) float waveAmplitude;// 振幅
    @property (assign, nonatomic) float waveSpeed;// 波纹流动的速度
    @property (assign, nonatomic) float waveOffset;// 初相
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self initialize];
        [self layoutUI];
        [self startWave];
    }
    
    
    #pragma mark - private method
    
    - (void)startWave {
        
        self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(wave:)];
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)stopWave {
        
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    
    - (void)wave:(CADisplayLink *)displayLink {
        
        self.waveOffset += self.waveSpeed;
        
        // 绘制第一条贝塞尔曲线
        UIBezierPath *path1 = [[UIBezierPath alloc] init];
        // 起始点
        [path1 moveToPoint:CGPointMake(0, self.waveAmplitude)];// 起始点
        // 遍历所有的x坐标,根据公式求出所有的y坐标,从而得到所有的点,贝塞尔曲线将它们连起来
        for (CGFloat x = 0.0; x < kScreenWidth; x ++) {
            
            CGFloat y = self.waveAmplitude * sinf(3 * M_PI * x / kScreenWidth + self.waveOffset * M_PI / kScreenWidth);
            [path1 addLineToPoint:CGPointMake(x, y)];
        }
        // 将贝塞尔曲线赋值给shapeLayer的path就可以了
        self.layer1.path = path1.CGPath;
        
        
        UIBezierPath *path2 = [[UIBezierPath alloc] init];
        [path2 moveToPoint:CGPointMake(0, self.waveAmplitude)];
        for (CGFloat x = 0.0; x < kScreenWidth; x ++) {
            
            CGFloat y = self.waveAmplitude * sinf(2.5 * M_PI * x / kScreenWidth + self.waveOffset * M_PI / kScreenWidth + M_PI / 4.0) + 3.0;
            [path2 addLineToPoint:CGPointMake(x, y)];
        }
        self.layer2.path = path2.CGPath;
        
        UIBezierPath *path3 = [[UIBezierPath alloc] init];
        [path3 moveToPoint:CGPointMake(0, self.waveAmplitude)];
        for (CGFloat x = 0.0; x < kScreenWidth; x ++) {
            
            CGFloat y = self.waveAmplitude * sinf(2 * M_PI * x / kScreenWidth + self.waveOffset * M_PI / kScreenWidth + M_PI / 2.0) + 6.0;
            [path3 addLineToPoint:CGPointMake(x, y)];
        }
        self.layer3.path = path3.CGPath;
    }
    
    
    #pragma mark - layoutUI
    
    - (void)layoutUI {
        
        [self.view addSubview:self.contentView];
        [self.contentView.layer addSublayer:self.layer1];
        [self.contentView.layer addSublayer:self.layer2];
        [self.contentView.layer addSublayer:self.layer3];
    }
    
    
    #pragma mark - setter, getter
    
    - (UIView *)contentView {
        
        if (_contentView == nil) {
            
            _contentView = [[UIView alloc] init];
            _contentView.frame = CGRectMake(0, kScreenHeight - 200, kScreenWidth, 200);
            _contentView.backgroundColor = [UIColor yellowColor];
        }
        
        return _contentView;
    }
    
    - (CAShapeLayer *)layer1 {
    
        if (_layer1 == nil) {
    
            _layer1 = [CAShapeLayer layer];
            _layer1.strokeColor = [UIColor redColor].CGColor;
            _layer1.fillColor = [UIColor clearColor].CGColor;
            _layer1.lineWidth =  5;
            _layer1.lineCap = kCALineCapRound;
            _layer1.lineJoin = kCALineJoinRound;
        }
        
        return _layer1;
    }
    
    - (CAShapeLayer *)layer2 {
    
        if (_layer2 == nil) {
    
            _layer2 = [CAShapeLayer layer];
            _layer2.strokeColor = [UIColor greenColor].CGColor;
            _layer2.fillColor = [UIColor clearColor].CGColor;
            _layer2.lineWidth =  5;
            _layer2.lineCap = kCALineCapRound;
            _layer2.lineJoin = kCALineJoinRound;
        }
        
        return _layer2;
    }
    
    - (CAShapeLayer *)layer3 {
    
        if (_layer3 == nil) {
    
            _layer3 = [CAShapeLayer layer];
            _layer3.strokeColor = [UIColor blueColor].CGColor;
            _layer3.fillColor = [UIColor clearColor].CGColor;
            _layer3.lineWidth =  5;
            _layer3.lineCap = kCALineCapRound;
            _layer3.lineJoin = kCALineJoinRound;
        }
        
        return _layer3;
    }
    
    
    #pragma mark - initialize
    
    - (void)initialize {
        
        // 初始值
        self.waveAmplitude = 20;
        self.waveSpeed = 2.0;
        self.waveOffset = 0.0;
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif

    二、CAGradientLayer颜色渐变

    CAGradientLayer是用来生成两种或更多种颜色渐变效果的,我们只需要将想要渐变的两个或多个颜色传给gradientLayer的colors数组属性,并设置一下gradientLayer颜色渐变的起点和终点确定下渐变色的朝向就可以了。

    例子:

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.navigationController.navigationBar.hidden = YES;
        
        // 基础渐变
        CAGradientLayer *naviLayer = [[CAGradientLayer alloc] init];
        naviLayer.frame = CGRectMake(0, 0, kScreenWidth, 64);
        naviLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];
        naviLayer.startPoint = CGPointMake(0, 0);// 单位坐标
        naviLayer.endPoint = CGPointMake(1, 1);
        [self.view.layer addSublayer:naviLayer];
        
        
        // 多重渐变:gradientLayer的colors属性是可以传很多个颜色的,默认情况下这些颜色在空间上被均匀地渲染,但是我们也可以通过locations数组来自定义每个color的位置,但是要注意如果使用locations属性就要保证它的数量和colors数组一一对应
        CAGradientLayer *customLayer = [[CAGradientLayer alloc] init];
        customLayer.frame = CGRectMake(100, 100, 100, 100);
        customLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor orangeColor].CGColor, (__bridge id)[UIColor yellowColor].CGColor];
        customLayer.locations = @[@(0), @(0.25), @(0.5)];// 这样颜色渐变就被挤在左上角的一半了
        customLayer.startPoint = CGPointMake(0, 0);
        customLayer.endPoint = CGPointMake(1, 1);
        [self.view.layer addSublayer:customLayer];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.png

    三、其它layer

    除了上面举的两个例子之外,还有CATextLayer、CAReplicatorLayer等很多专用图层,我们可以根据自己的需求学习它们。

    相关文章

      网友评论

          本文标题:第五篇:CALayer能力之专用图层

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