ObjC贝塞尔曲线实现渐变带动画的信用圆环,参考示例代码:
WangCreditView.h:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 信用圆环
*/
@interface WangCreditView : UIView
/** 当前显示的值(以及刻度值) */
@property(nonatomic, assign) float currentScaleVaue;
/** 刻度最大值(默认 1000) */
@property(nonatomic, assign) float maxScaleVaue;
/** 刻度最小值(默认 0) */
@property(nonatomic, assign) float minScaleVaue;
/** 评估时间(默认当前时间) */
@property(nonatomic, copy) NSString *strAssessDate;
/** 描述信息(为空不显示,默认为空) */
@property(nonatomic, copy) NSString *strDescription;
/** 内外环中间间距(默认 15.0) */
@property(nonatomic, assign) CGFloat lineMargin;
/** 开口角度 */
@property(nonatomic, assign) float startAngle;
/** 闭合角度 */
@property(nonatomic, assign) float endAngle;
//
/** 内线宽(默认 1.0) */
@property(nonatomic, assign) CGFloat inLineWidth;
/** 内线颜色(默认 白色) */
@property(nonatomic,assign) UIColor *inLineColor;
/** 外虚线宽(默认 5.0) */
@property(nonatomic, assign) CGFloat outLineWidth;
/** 外虚线颜色(默认 白色) */
@property(nonatomic,assign) UIColor *outLineColor;
/** 初始化后,调用此方法绘制(动画效果) */
-(void)startAnimationDraw;
/** 初始化后,调用此方法绘制(与上面方法二选一即可) */
-(void)startDraw;
@end
NS_ASSUME_NONNULL_END
WangCreditView.m:
#import "WangCreditView.h"
//当前中心显示值字体、颜色
#define current_font_size 25.0
#define current_font_color [UIColor whiteColor]
//当前描述信息
#define description_font_size 14.0
#define description_font_color [UIColor whiteColor]
//当前评估信息
#define assess_font_size 14.0
#define assess_font_color [UIColor whiteColor]
//当前视图宽、高
#define view_width self.frame.size.width
#define view_height self.frame.size.height
#define layer_gradient_view_tag 12345
@implementation WangCreditView{
//当前仪表值
UILabel *labCurrentInfo;
//描述信息
UILabel *labDescription;
//评估时间
UILabel *labAssessDate;
//内线
CAShapeLayer *inLineLayer;
CAShapeLayer *inLineLayerBG;
//外虚线
CAShapeLayer *outLineLayer;
CAShapeLayer *outLineLayerBG;
CALayer *outGradientLayer;
//动画
CABasicAnimation *animation;
//最小刻度
UILabel *labMinInfo;
//最大刻度
UILabel *labMaxInfo;
BOOL isAnimation;
}
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self initView];
}
return self;
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
[self initView];
}
return self;
}
-(void)dealloc{
NSLog(@"dealloc");
[self unInitObserver];
[self clearLayer];
}
//MARK: - initView
-(void)initView{
CGFloat h;
CGFloat w;
CGFloat x;
CGFloat y;
CGRect rect;
//MARK:当前仪表值
if (!labCurrentInfo) {
h = 21.0;
w = 100.0;
x = (self.frame.size.width - w) * 0.5;
y = (self.frame.size.height - h) * 0.5;
rect = CGRectMake(x, y, w, h);
labCurrentInfo = [[UILabel alloc] initWithFrame:rect];
labCurrentInfo.font = [UIFont boldSystemFontOfSize:current_font_size];
labCurrentInfo.textColor = current_font_color;
labCurrentInfo.adjustsFontSizeToFitWidth = YES;
labCurrentInfo.textAlignment = NSTextAlignmentCenter;
[self addSubview:labCurrentInfo];
}
//MARK:当前描述信息
if (!labDescription) {
h = 21.0;
w = 100.0;
x = (self.frame.size.width - w) * 0.5;
y = labCurrentInfo.frame.origin.y + labCurrentInfo.frame.size.height + 10.0;
rect = CGRectMake(x, y, w, h);
labDescription = [[UILabel alloc] initWithFrame:rect];
labDescription.font = [UIFont systemFontOfSize:description_font_size];
labDescription.textColor = description_font_color;
labDescription.adjustsFontSizeToFitWidth = YES;
labDescription.textAlignment = NSTextAlignmentCenter;
[self addSubview:labDescription];
}
//MARK:评估时间
if (!labAssessDate) {
h = 21.0;
w = 100.0;
x = (self.frame.size.width - w) * 0.5;
y = self.frame.size.height - h - 10.0;
rect = CGRectMake(x, y, w, h);
labAssessDate = [[UILabel alloc] initWithFrame:rect];
labAssessDate.font = [UIFont systemFontOfSize:assess_font_size];
labAssessDate.textColor = assess_font_color;
labAssessDate.adjustsFontSizeToFitWidth = YES;
labAssessDate.textAlignment = NSTextAlignmentCenter;
[self addSubview:labAssessDate];
}
//监听
[self initObserver];
//[S] 初始值
self.inLineWidth = 1.0;
self.outLineWidth = 5.0;
self.lineMargin = 15.0;
self.inLineColor = [UIColor whiteColor];
self.outLineColor = [UIColor whiteColor];
self.maxScaleVaue = 1000.0;
self.minScaleVaue = 0.0;
self.startAngle = 0.785 * M_PI;
self.endAngle = 0.230 * M_PI;
self.strDescription = @"";
self.strAssessDate = [NSString stringWithFormat:@"评估 %@",[Utils getCurrentDateToString:@"yyyy-MM-dd"]];
//[E] 初始值
}
//MARK: - initObserver
-(void)initObserver{
//当前值
[self addObserver:self forKeyPath:@"currentScaleVaue" options:NSKeyValueObservingOptionNew context:nil];
//评估时间
[self addObserver:self forKeyPath:@"strAssessDate" options:NSKeyValueObservingOptionNew context:nil];
//描述信息
[self addObserver:self forKeyPath:@"strDescription" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)unInitObserver{
[self removeObserver:self forKeyPath:@"currentScaleVaue"];
[self removeObserver:self forKeyPath:@"strAssessDate"];
[self removeObserver:self forKeyPath:@"strDescription"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
id _newValue = [change valueForKey:@"new"];
//当前值
if ([keyPath isEqualToString:@"currentScaleVaue"]) {
self->labCurrentInfo.text = [NSString stringWithFormat:@"%.f",[_newValue floatValue]];
}
//评估时间
else if([keyPath isEqualToString:@"strAssessDate"]){
self->labAssessDate.text = _newValue;
}
//描述信息
else if([keyPath isEqualToString:@"strDescription"]){
self->labDescription.text = _newValue;
}
}
//MARK: - drawRect
/** 初始化后,调用此方法绘制(动画效果) */
-(void)startAnimationDraw{
[self clearLayer];
//初始化动画
if (!animation) {
animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = 3.0;
animation.fromValue = @0.0;
animation.toValue = @1.0;
animation.repeatCount = 1;
}
isAnimation = YES;
[self drayinLineLayer];
[self drayOutLineLayer];
[self drayCalibration];
}
/** 初始化后,调用此方法绘制(与上面方法二选一即可) */
-(void)startDraw{
[self clearLayer];
//清除动画
[self clearAnimation];
isAnimation = NO;
[self drayinLineLayer];
[self drayOutLineLayer];
[self drayCalibration];
}
//清除
-(void)clearLayer{
[self clearAnimation];
if (inLineLayer) {
[inLineLayer removeFromSuperlayer];
inLineLayer = nil;
}
if (inLineLayerBG) {
[inLineLayerBG removeFromSuperlayer];
inLineLayerBG = nil;
}
if (outLineLayer) {
[outLineLayer removeFromSuperlayer];
outLineLayer = nil;
}
if (outLineLayerBG) {
[outLineLayerBG removeFromSuperlayer];
outLineLayerBG = nil;
}
if (labMinInfo) {
[labMinInfo removeFromSuperview];
labMinInfo = nil;
}
if (labMaxInfo) {
[labMaxInfo removeFromSuperview];
labMaxInfo = nil;
}
if (outGradientLayer) {
[outGradientLayer removeFromSuperlayer];
outGradientLayer = nil;
}
}
-(void)clearAnimation{
if (animation) {
animation = nil;
}
}
//根据当前刻度值获取闭口角度
-(CGFloat)getEndAngle{
//参考:https://developer.apple.com/documentation/uikit/uibezierpath/1624358-bezierpathwitharccenter?language=objc
if (self.currentScaleVaue >= self.maxScaleVaue) {
return self.endAngle;
}
else if(self.currentScaleVaue <= self.minScaleVaue){
return self.startAngle;
}
else{
CGFloat _bl = (self.currentScaleVaue - self.minScaleVaue) / (self.maxScaleVaue - self.minScaleVaue);
return M_PI + M_PI * _bl;
}
}
//MARK: - 绘制刻度
/** 绘制刻度 */
-(void)drayCalibration{
CGRect rect = CGRectMake(0, 0, 40, 12);
UIFont *font = [UIFont systemFontOfSize:10];
UIColor *color = [UIColor whiteColor];
//最小值
if(!labMinInfo){
rect.origin = labAssessDate.frame.origin;
rect.origin.y -= 2.25 * rect.size.height;
rect.origin.x -= 1.15 * self.lineMargin;
labMinInfo = [[UILabel alloc] initWithFrame:rect];
labMinInfo.text = [NSString stringWithFormat:@"%.f",self.minScaleVaue];
labMinInfo.textColor = color;
labMinInfo.font = font;
labMinInfo.textAlignment = NSTextAlignmentCenter;
//顺时针旋转(- 逆时针旋转)
labMinInfo.transform = CGAffineTransformMakeRotation(-M_PI*0.65);
[self addSubview:labMinInfo];
}
//最大值
if(!labMaxInfo){
rect.origin = labAssessDate.frame.origin;
rect.origin.y -= 2.45 * rect.size.height;
rect.origin.x += 3.75 * self.lineMargin + rect.size.width;
labMaxInfo = [[UILabel alloc] initWithFrame:rect];
labMaxInfo.text = [NSString stringWithFormat:@"%.f",self.maxScaleVaue];
labMaxInfo.textColor = color;
labMaxInfo.font = font;
labMaxInfo.textAlignment = NSTextAlignmentCenter;
//顺时针旋转(- 逆时针旋转)
labMaxInfo.transform = CGAffineTransformMakeRotation(-M_PI*0.35);
[self addSubview:labMaxInfo];
}
}
//MARK: - 绘制内线及背景
/**
* 绘制内线
* 参考:https://www.cnblogs.com/ioshe/p/5481841.html
*/
-(void)drayinLineLayer{
if (!inLineLayer) {
inLineLayer = [CAShapeLayer layer];
if (animation) {
[inLineLayer addAnimation:animation forKey:@"strokeEndAnimation"];
}
}
CGFloat x = 0.0;
CGFloat y = 0.0;
CGFloat w = view_width - 2 * self.outLineWidth - 4 * self.lineMargin;
CGFloat h = labAssessDate.frame.origin.y - 3 * self.lineMargin - self.outLineWidth;
CGRect rect = CGRectMake(x, y, w, h);
inLineLayer.frame = rect;
inLineLayer.position = CGPointMake(view_width * 0.5, view_height * 0.5);
//填充颜色(此处为透明填充)
inLineLayer.fillColor = [UIColor clearColor].CGColor;
//线条宽度
inLineLayer.lineWidth = self.inLineWidth;
//线条颜色
inLineLayer.strokeColor = self.inLineColor.CGColor;
//设置stroke起始点
//参考:https://blog.csdn.net/yixiangboy/article/details/50662704
//inLineLayer.strokeStart = 0.38;
//inLineLayer.strokeEnd = 1.0;
//创建出圆形贝塞尔曲线
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:[self getCenterPoint:1]
radius:rect.size.height * 0.5
startAngle:_startAngle
endAngle:[self getEndAngle]
clockwise:YES];// 是否顺时针方向
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
//按照塞尔曲线绘制
inLineLayer.path = bezierPath.CGPath;
// [S] 绘制背景
inLineLayerBG = [CAShapeLayer layer];
inLineLayerBG.frame = inLineLayer.frame;
inLineLayerBG.position = inLineLayer.position;
inLineLayerBG.fillColor = [UIColor clearColor].CGColor;
inLineLayerBG.lineWidth = self.inLineWidth;
inLineLayerBG.strokeColor = [UIColor lightGrayColor].CGColor;
bezierPath = [UIBezierPath bezierPathWithArcCenter:[self getCenterPoint:1]
radius:rect.size.height * 0.5
startAngle:_startAngle
endAngle:_endAngle
clockwise:YES];
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
inLineLayerBG.path = bezierPath.CGPath;
// [E] 绘制背景
[self.layer addSublayer:inLineLayerBG];
[self.layer addSublayer:inLineLayer];
}
//MARK: - 绘制外虚线及背景
-(void)drayOutLineLayer{
if (!outLineLayer) {
outLineLayer = [CAShapeLayer layer];
if (animation) {
[outLineLayer addAnimation:animation forKey:@"strokeEndAnimation"];
}
}
CGFloat x = 0.0;
CGFloat y = 0.0;
CGFloat w = view_width - 2 * self.lineMargin;
CGFloat h = labAssessDate.frame.origin.y;
CGRect rect = CGRectMake(x, y, w, h);
outLineLayer.frame = rect;
outLineLayer.position = CGPointMake(view_width * 0.5, view_height * 0.5);
//填充颜色(此处为透明填充)
outLineLayer.fillColor = [UIColor clearColor].CGColor;
//线条宽度
outLineLayer.lineWidth = self.outLineWidth;
//[outLineLayer setLineJoin:kCALineJoinRound];
//1=线的宽度 3=每条线的间距
NSArray *_temp = [NSArray arrayWithObjects:@1,@3,nil];
[outLineLayer setLineDashPattern:_temp];
//线条颜色
outLineLayer.strokeColor = self.outLineColor.CGColor;
//设置stroke起始点
//参考:https://blog.csdn.net/yixiangboy/article/details/50662704
//outLineLayer.strokeStart = 0.50;
//outLineLayer.strokeEnd = 1.0;
//创建出圆形贝塞尔曲线
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:[self getCenterPoint:2]
radius:rect.size.height * 0.5
startAngle:_startAngle
endAngle:[self getEndAngle]
clockwise:YES];
//按照贝塞尔曲线绘制
outLineLayer.path = bezierPath.CGPath;
// [S] 绘制背景
outLineLayerBG = [CAShapeLayer layer];
outLineLayerBG.frame = outLineLayer.frame;
outLineLayerBG.position = outLineLayer.position;
outLineLayerBG.fillColor = [UIColor clearColor].CGColor;
outLineLayerBG.lineWidth = self.outLineWidth;
outLineLayerBG.strokeColor = [UIColor lightGrayColor].CGColor;
bezierPath = [UIBezierPath bezierPathWithArcCenter:[self getCenterPoint:2]
radius:rect.size.height * 0.5
startAngle:_startAngle
endAngle:_endAngle
clockwise:YES];
[outLineLayerBG setLineJoin:kCALineJoinRound];
[outLineLayerBG setLineDashPattern:_temp];
outLineLayerBG.path = bezierPath.CGPath;
// [E] 绘制背景
[self.layer addSublayer:outLineLayerBG];
//非渐变填充
//[self.layer addSublayer:outLineLayer];
//渐变填充
outGradientLayer = [self drayColor:outLineLayer];
[self.layer addSublayer:outGradientLayer];
}
//type 1 内线 2 外线
-(CGPoint)getCenterPoint:(NSInteger)type{
CGPoint point = CGPointMake(view_width * 0.5, view_height * 0.5);
//内线
if (type == 1) {
point.x -= 23.0;
point.y -= 23.0;
}
//外线
else if (type == 2){
point.x -= 10.0;
point.y -= 10.0;
}
return point;
}
//MARK: - 绘制渐变色
-(CALayer *)drayColor:(CAShapeLayer *)circle{
//创建第一个gradientLayer
NSArray *colors1 = @[
(__bridge id)[UIColor colorWithRed:49.f/255.f green:240.f/255.f blue:233.f/255.f alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:69.f/255.f green:240.f/255.f blue:210.f/255.f alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:143.f/255.f green:238.f/255.f blue:147.f/255.f alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:220.f/255.f green:237.f/255.f blue:92.f/255.f alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:253.f/255.f green:237.f/255.f blue:74.f/255.f alpha:1.0].CGColor
];
CGRect frame1 = CGRectMake(0.0, 0.0, circle.frame.size.width * 0.5, circle.frame.size.height);
CAGradientLayer *gradientLayer1 = [CAGradientLayer layer];
gradientLayer1.startPoint = CGPointMake(0.5, 0.0);
gradientLayer1.endPoint = CGPointMake(0.5, 1.0);
gradientLayer1.frame = frame1;
gradientLayer1.colors = colors1;
NSArray *colors2 = @[
(__bridge id)[UIColor colorWithRed:253.f/255.f green:237.f/255.f blue:74.f/255.f alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:224.f/255.f green:238.f/255.f blue:59.f/255.f alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:148.f/255.f green:238.f/255.f blue:146.f/255.f alpha:1.0].CGColor
];
CGRect frame2 = CGRectMake(circle.frame.size.width * 0.5, 0.0, circle.frame.size.width * 0.5, circle.frame.size.height);
CAGradientLayer *gradientLayer2 = [CAGradientLayer layer];
gradientLayer2.startPoint = CGPointMake(0.5, 1.0);
gradientLayer2.endPoint = CGPointMake(0.5, 1.0);
gradientLayer2.frame = frame2;
gradientLayer2.colors = colors2;
//用一个gradientLayer来承接这两个渐变区域
CALayer *gradientLayer = [CALayer layer];
gradientLayer.frame = circle.bounds;
[gradientLayer addSublayer:gradientLayer1];
[gradientLayer addSublayer:gradientLayer2];
//用circle的path形状来截取渐变区域
gradientLayer.mask = circle;
return gradientLayer;
}
@end
调用实例:
//仪表
@property (nonatomic,strong) WangCreditView *creditView;
//MARK: - 信用仪表盘
-(WangCreditView *)creditView{
if (!_creditView) {
CGFloat y = 0.0;
CGFloat h = table_head_view_height - split_line_height;
CGFloat w = h;
CGFloat x = (K_APP_WIDTH - w) * 0.5;
CGRect rect = CGRectMake(x, y, w, h);
_creditView = [[WangCreditView alloc] initWithFrame:rect];
_creditView.backgroundColor = [UIColor clearColor];
_creditView.minScaleVaue = 0.0;
_creditView.maxScaleVaue = 350.0;
_creditView.lineMargin = 10.0;
_creditView.strAssessDate = [NSString stringWithFormat:@"评估 %@",[Utils getCurrentDateToString:@"yyyy-MM-dd"]];
_creditView.strDescription = @"信用小白";
//获取值
[self updateCreditView];
//开始绘制
[_creditView startAnimationDraw];
}
return _creditView;
}
-(void)updateCreditView{
NSString *strInfo = @"信用小白";
CGFloat floatValue = 0.0;
if ([self userIsLogin]) {
NSInteger i = 0;
if (K_APP_R_STATUS_PASS) i++;
if (K_APP_Y_STATUS_PASS) i++;
if (K_APP_BANK_STATUS_PASS) i++;
if (K_APP_ZFB_STATUS_PASS) i++;
if (K_APP_GRXX_STATUS_PASS) i++;
floatValue = i * 50.0;
if(floatValue <= 50){
strInfo = @"信用小白";
}
else if (floatValue <= 200) {
strInfo = @"信用良好";
}
else{
strInfo = @"信用优秀";
}
}
self.creditView.currentScaleVaue = floatValue;
self.creditView.strDescription = strInfo;
}
效果图如下: