目录
一、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等很多专用图层,我们可以根据自己的需求学习它们。
网友评论