渐变图层通常与插值贝塞尔曲线混合使用来绘制各种变化曲线。刚开始也是公司需求,要绘制这种光滑的曲线,我也找了很多的第三方,带动画的、不带动画的种种,结果都不满意,然后就开始自己搞,其中同事也给了我很多提示。
贝塞尔曲线可能都会画,但是于要求还远远不够,怎么画出光滑的曲线,再加上渐变的图层,这就是关键了,下面的代码主要讲解处理插值数据,让贝塞尔曲线更加光滑,通过贝塞尔曲线绘制渐变图层。俗话说授人以鱼不如授人以渔,下面就以代码来讲解实现和使用的方法,让初学者都能自己写自己想要的曲线图。
讲解全靠代码注释
GradeColorView类,.h文件
#import <UIKit/UIKit.h>
@interface GradeColorView : UIView
@property(strong,nonatomic)UIColor * lineColor;/////线的颜色
@property (nonatomic, strong)NSMutableArray *allPoints;///所有点包括插值点
- (instancetype)initWithFrame:(CGRect)frame;
- (void)updateUIWithPointArray:(NSArray *)pointsArray insertNum:(NSInteger)insertNum withLineColor:(UIColor *)color;
@end
.m文件
#import "GradeColorView.h"
#import "DataGetY.h"
///把对应的颜色
#define kSetAlColor(rd,ge,be,al) ([UIColor colorWithRed:rd<=1?rd:rd/255.0 green:ge<=1?ge:ge/255.0 blue:be<=1?be:be/255.0 alpha:al])
//获取颜色R G B
#define kGetColorRed(color) ([[[CIColor alloc]initWithColor:color] red])
#define kGetColorGreen(color) ([[[CIColor alloc]initWithColor:color] green])
#define kGetColorBlue(color) ([[[CIColor alloc]initWithColor:color] blue])
@interface GradeColorView ()
@property (nonatomic, strong)CAGradientLayer *gradientLayer;////渐变色层
@property (nonatomic, strong)NSArray *pointsArray;////曲线的点数组
@property (nonatomic, assign)NSInteger insertNum;
@end
@implementation GradeColorView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.allPoints = [[NSMutableArray alloc]init];
}
return self;
}
////传入数据 数组
- (void)updateUIWithPointArray:(NSArray *)pointsArray insertNum:(NSInteger)insertNum withLineColor:(UIColor *)color
{
self.lineColor = color;///设置曲线颜色
self.insertNum = insertNum;
self.pointsArray = pointsArray;
////创建BezierPath
UIBezierPath *path = [UIBezierPath bezierPath];
////对数据进行处理,开始插值,为渐变色图层提供范围
[self makeDownPointWithInsertNum:insertNum andPointsArray:self.pointsArray withPath:path];
path.lineWidth = 0;
[path stroke];
///self.gradientLayer为渐变色层
CAShapeLayer *layer = [CAShapeLayer layer];////创建一个Layer层
layer.path = [path CGPath];
layer.lineCap = kCALineCapRound;
self.gradientLayer.mask = layer;/////layer的背景色选中区域
[self.layer addSublayer:self.gradientLayer];
[self setNeedsDisplay];
}
-(void)drawRect:(CGRect)rect
{
[super drawRect:rect];
UIBezierPath *path = [UIBezierPath bezierPath];
[self makeDownPointWithInsertNum:self.insertNum andPointsArray:self.pointsArray withPath:path];
[self.lineColor setStroke];
[path stroke];
}
////对数据进行插值处理 params: num参数表示插值的点数,自己定,点数越多,曲线越光滑 pointsArray参数表示要处理的所有点
- (void)makeDownPointWithInsertNum:(NSInteger)num andPointsArray:(NSArray *)pointsArray withPath:(UIBezierPath *)path
{
/*
插值法 使用说明 p1 和 p2 之间插值 要计算 p0 和 p3
其中 p0 是 p1 前面的一个点(如果p1已经是数组里第一个点了,那p0就是p1点左下方一点,计算如例)
p3 是p2 后面的一个点(如果p2已经是最后一个点了,那p3就是p2正右上方的点)
|
|
|
| .p2
|
|
| .p3
| .p1
|
|.p0
|
|__________________________________________________
*/
for (int i = 0; i < pointsArray.count-1; i ++) {
CGPoint p1 = CGPointFromString(pointsArray[i]);
CGPoint p0;
if (i == 0) {
p0 = CGPointMake(p1.x-2, p1.y-2);
[path moveToPoint:p1];
}else{
[path addLineToPoint:p1];
p0 = CGPointFromString(pointsArray[i-1]);
}
CGPoint p2 = CGPointFromString(pointsArray[i + 1]);
CGPoint p3 ;
if (i == pointsArray.count - 2) {
p3 = CGPointMake(p1.x+2, p1.y+2);
}else{
p3 = CGPointFromString(pointsArray[i + 2]);
}
////这个是插值算法,不讲了,固定写法,因为我也不会,装B装漏了 -_-|
for (int i = 1; i <= num; i++) {
float t = i * (1.0f / num);
float tt = t * t;
float ttt = tt * t;
CGFloat pointx_x = (float) (0.5 * (2 * p1.x + (p2.x - p0.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * tt + (3 * p1.x - p0.x - 3 * p2.x + p3.x)
* ttt));
CGFloat pointx_y = (float) (0.5 * (2 * p1.y + (p2.y - p0.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * tt + (3 * p1.y - p0.y - 3 * p2.y + p3.y)
* ttt));
[path addLineToPoint:CGPointMake(pointx_x, pointx_y)];
}
}
}
#pragma mark -- getter/setter 专门负责添加渐变色
- (CAGradientLayer *)gradientLayer
{
if (!_gradientLayer) {
_gradientLayer = [CAGradientLayer layer];
_gradientLayer.frame = self.bounds;
_gradientLayer.colors = @[(__bridge id)kSetAlColor(kGetColorRed(_lineColor), kGetColorGreen(_lineColor), kGetColorBlue(_lineColor), 0.8).CGColor,(__bridge id)kSetAlColor(kGetColorRed(_lineColor), kGetColorGreen(_lineColor), kGetColorBlue(_lineColor), 0.1).CGColor];///其中渐变层的颜色种类,alpha表示所占的百分比
_gradientLayer.startPoint = CGPointMake(0, 0);///表示开始的方位(左 ,上)
_gradientLayer.endPoint = CGPointMake(0, 1);////结束方位(右,下)
}
return _gradientLayer;
}
@end
最后是封装的曲线类的使用
- (void)viewDidLoad {
[super viewDidLoad];
GradeColorView *colorView = [[GradeColorView alloc]initWithFrame:CGRectMake(80, 120, 250, 250)];
[colorView updateUIWithPointArray:@[NSStringFromCGPoint(CGPointMake(0,250)),NSStringFromCGPoint(CGPointMake(20, 160)),NSStringFromCGPoint(CGPointMake(40, 100)),NSStringFromCGPoint(CGPointMake(60, 20)),NSStringFromCGPoint(CGPointMake(80, 90)),NSStringFromCGPoint(CGPointMake(100, 120)),NSStringFromCGPoint(CGPointMake(120, 80)),NSStringFromCGPoint(CGPointMake(140, 140)),NSStringFromCGPoint(CGPointMake(160, 190)),NSStringFromCGPoint(CGPointMake(250, 250))] insertNum:19 withLineColor:[UIColor orangeColor]];
[self.view addSubview:colorView];
}
在下面是数组元素转化便于数组存取的DataGetY类
.h文件
#import <Foundation/Foundation.h>
#define Str(f) [NSString stringWithFormat:@"%f",f]
@interface DataGetY : NSObject
- (NSMutableDictionary *)makeDownDataWithDataArray:(NSArray *)points;
@end
.m文件
#import "DataGetY.h"
#import <UIKit/UIKit.h>
@implementation DataGetY
- (NSMutableDictionary *)makeDownDataWithDataArray:(NSArray *)points
{
if (points.count > 0) {
NSMutableDictionary *dic;
for (NSString *pointStr in points) {
CGPoint point = CGPointFromString(pointStr);
[dic setObject:Str(point.y) forKey:Str(point.x)];
}
return dic;
}
return nil;
}
@end
下面是插值曲线的效果: