Due to business needs, a customized dial control
1: Scale values and pointers support color gradients
2: Because I wrote it all by myself and use it myself, there are not many ways to modify it. If you want to use it, you can take a good look at the source code. The implementation principle is actually not difficult, just learn it.
android version: https://blog.csdn.net/qq_15327175/article/details/134155127
Look at the renderings, animations and numerical changes. I won’t show them here. Friends with similar needs can do their own research.
WBBDashBoard.h
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface WBBDashBoard : UIView @property (nonatomic, assign) float value; @end NS_ASSUME_NONNULL_END
WBBDashBoard.m
#import "WBBDashBoard.h" #define toRad(angle) ((angle) * M_PI / 180) @interface WBBDashBoard () /** * pointer center */ @property (nonatomic, assign) CGPoint dotCenter; /** *Instrument radius */ @property (nonatomic, assign) CGFloat radius; /** * Disk starting angle */ @property(nonatomic,assign)CGFloat startAngle; /** * Disc end angle */ @property(nonatomic,assign)CGFloat endAngle; /** * The total number of radians of the disk */ @property(nonatomic,assign)CGFloat arcAngle; /** * pointer */ @property (nonatomic, strong) CAShapeLayer *pointLayer; /** * Pointer fixed point */ @property (nonatomic, strong) CAShapeLayer *bPointLayer; @property (nonatomic, strong) UILabel *valueLable; /** * Dial value width */ @property(nonatomic,assign) CGFloat valueW; /** * Dial value is high */ @property(nonatomic,assign)CGFloat valueH; /** *maximum */ @property(nonatomic,assign)CGFloat maxValue; /** * minimum value */ @property(nonatomic,assign)CGFloat minValue; /** * Gradient circle */ @property (nonatomic, strong)CAShapeLayer* arcLayer; @end @implementation WBBDashBoard /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ - (instancetype)initWithFrame:(CGRect)frame {<!-- --> if (self = [super initWithFrame:frame]) {<!-- --> _value=1.0f; self.dotCenter = CGPointMake(frame.size.width/2.0, frame.size.width/2.0); self.radius = frame.size.width/2.0; self.startAngle=toRad(-225); self.endAngle=toRad(45); self.valueW=24.f; self.valueH=16.f; self.maxValue=3.0f; self.minValue=1.0f; self.arcAngle= fabsf(self.startAngle - self.endAngle); [self loadSubViews]; } return self; } - (void)loadSubViews {<!-- --> [self drawArcWithStartAngle]; [self DrawScaleValue]; [self drawPoint]; [self setTitle:@"PUE"]; } /** * Draw arc * * @param startAngle starting angle * @param endAngle end angle * @param lineWitdth line width * @param filleColor sector fill color * @param strokeColor arc color */ -(void)drawArcWithStartAngle{<!-- --> //Inner loop Mask UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius startAngle:_startAngle endAngle:_endAngle clockwise:YES]; CAShapeLayer* shapeLayer1=[CAShapeLayer layer]; shapeLayer1.lineWidth=3; shapeLayer1.fillColor=[UIColor clearColor].CGColor; shapeLayer1.path=outArc.CGPath; shapeLayer1.strokeColor=[UIColor whiteColor].CGColor; [self.layer addSublayer:shapeLayer1]; double ArcLenght=0; // //Mask outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-4 startAngle:_startAngle endAngle:_endAngle clockwise:YES]; CAShapeLayer* shapeLayer2=[CAShapeLayer layer]; shapeLayer2.lineWidth=4; shapeLayer2.fillColor=[UIColor clearColor].CGColor; shapeLayer2.path=outArc.CGPath; ArcLenght=((2*M_PI*(_radius-4)*3/4)-21*1)/20; [shapeLayer2 setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithDouble:1], [NSNumber numberWithDouble:ArcLenght], nil]];//0: length 1: gap shapeLayer2.strokeColor=[UIColor whiteColor].CGColor; [self.layer addSublayer:shapeLayer2]; outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-4 startAngle:_startAngle endAngle:_endAngle clockwise:YES]; CAShapeLayer* shapeLayer3=[CAShapeLayer layer]; shapeLayer3.lineWidth=6; shapeLayer3.fillColor=[UIColor clearColor].CGColor; shapeLayer3.path=outArc.CGPath; ArcLenght=((2*M_PI*(_radius-4)*3/4)-5*2)/4; [shapeLayer3 setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithDouble:2], [NSNumber numberWithDouble:ArcLenght], nil]];//0: length 1: gap shapeLayer3.strokeColor=[UIColor whiteColor].CGColor; [self.layer addSublayer:shapeLayer3]; } /** * Draw the scale value, set the label value counterclockwise, divide the entire instrument into N parts, and increment by 1/N of the instrument panel radian each time * * @param divide how many equal parts the scale value is */ -(void)DrawScaleValue{<!-- --> CGFloat textAngel =self.arcAngle/4; for (NSUInteger i = 0; i <= 4; i + + ) {<!-- --> CGPoint point = [self calculateTextPositonWithArcCenter:_dotCenter Angle:(self.startAngle + textAngel*i)]; NSString *tickText; if (i == 0) {<!-- --> tickText =@"1.0"; } else if (i == 4) {<!-- --> tickText =@"3.0"; } else {<!-- --> tickText = [NSString stringWithFormat:@"%.1f",(3.0f-1.0f)/4.0f*i + 1.0f]; } //The default label size is 23 * 14 UILabel *text = [[UILabel alloc] initWithFrame:CGRectMake(point.x, point.y, _valueW, _valueH)]; text.text = tickText; text.font = [UIFont systemFontOfSize:12.f]; text.textColor = [UIColor whiteColor]; // text.backgroundColor=[UIColor greenColor]; text.textAlignment = NSTextAlignmentCenter; [self addSubview:text]; } } //The default calculation radius is -10, and the coordinates of the label are calculated - (CGPoint)calculateTextPositonWithArcCenter:(CGPoint)center Angle:(CGFloat)angel {<!-- --> CGFloat x = center.x + (_radius)* cosf(angel); CGFloat y = center.y + (_radius)* sinf(angel); // y=y-_valueH/2; if(angel>=toRad(5) & amp; & amp;angel<=toRad(85)||angel>=toRad(-275) & amp; & amp;angel<=toRad(-355)){<! -- --> y=y-_valueH/2; x=x + _valueW/3; }else if(angel>toRad(85) & amp; & amp;angel<toRad(95)||angel>toRad(-265) & amp; & amp;angel<toRad(-275)){<!-- --> y=y-_valueH/2 + _valueW/3; x=x-x + _valueW/2; } else if(angel>=toRad(95) & amp; & amp;angel<=toRad(175)||angel>=toRad(-265) & amp; & amp;angel<=toRad(-185)){< !-- --> y=y-_valueH/2; x=x-_valueW-_valueW/3; }else if(angel>toRad(175) & amp; & amp;angel<toRad(185)||angel>toRad(-185) & amp; & amp;angel<toRad(-175)){<!-- --> y=y-_valueH/2; x=x-_valueW-_valueW/3; } else if(angel>=toRad(185) & amp; & amp;angel<=toRad(265)||angel>=toRad(-175) & amp; & amp;angel<=toRad(-95)){< !-- --> y=y-_valueH/2; x=x-_valueW-_valueW/3; }else if(angel>toRad(265) & amp; & amp;angel<toRad(275)||angel>toRad(-95) & amp; & amp;angel<toRad(-85)){<!-- --> y=y-_valueH; x=x-_valueW/2; } else if(angel>=toRad(275) & amp; & amp;angel<=toRad(355)||angel>=toRad(-85) & amp; & amp;angel<=toRad(-5)){< !-- --> y=y-_valueH/2; x=x + _valueW/3; }else if(angel>toRad(355) & amp; & amp;angel<toRad(5)||angel>toRad(-5) & amp; & amp;angel<toRad(5)){<!-- - -> y=y-_valueH/2; x=x + _valueW/3; } // NSLog([NSString stringWithFormat:@"0:%f",toRad(0)]); // NSLog(@"-------------------------------------------------- ----------------------------------"); // NSLog([NSString stringWithFormat:@"90:%f",toRad(90)]); // NSLog([NSString stringWithFormat:@"180:%f",toRad(180)]); // NSLog([NSString stringWithFormat:@"270:%f",toRad(270)]); // NSLog(@"-------------------------------------------------- ----------------------------------"); // NSLog([NSString stringWithFormat:@"-90:%f",toRad(-90)]); // NSLog([NSString stringWithFormat:@"-180:%f",toRad(-180)]); // NSLog([NSString stringWithFormat:@"-270:%f",toRad(-270)]); return CGPointMake(x, y); } -(void)setTitle:(NSString *)title {<!-- --> UILabel *text = [[UILabel alloc] initWithFrame:CGRectMake(self.dotCenter.x-25, self.dotCenter.y + 20, 50, 20)]; text.text = title; text.font = [UIFont systemFontOfSize:12.f]; text.textColor = [UIColor whiteColor]; // text.backgroundColor=[UIColor greenColor]; text.textAlignment = NSTextAlignmentCenter; [self addSubview:text]; _valueLable=[[UILabel alloc] initWithFrame:CGRectMake(self.dotCenter.x-25, self.dotCenter.y + 40, 50, 20)]; _valueLable.text=@"--"; _valueLable.textAlignment=NSTextAlignmentCenter; _valueLable.font=[UIFont systemFontOfSize:14]; _valueLable.textColor=[UIColor whiteColor]; [self addSubview:_valueLable]; } - (void)drawPoint {<!-- --> UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-3 startAngle:_startAngle endAngle:_startAngle clockwise:YES]; _arcLayer=[CAShapeLayer layer]; _arcLayer.lineWidth=6; _arcLayer.fillColor=[UIColor clearColor].CGColor; _arcLayer.path=outArc.CGPath; _arcLayer.strokeColor=[UIColor whiteColor].CGColor; NSArray *colors1= [NSArray arrayWithObjects: (id)[[UIColor kh_colorWithHexString:@"36e6fd" andAlpha:0.2] CGColor], (id)[[UIColor kh_colorWithHexString:@"60df5a" andAlpha:0.6] CGColor], (id)[[UIColor kh_colorWithHexString:@"efdf1b" andAlpha:0.7] CGColor],nil]; NSArray *colors2= [NSArray arrayWithObjects: (id)[[UIColor kh_colorWithHexString:@"efdf1b" andAlpha:0.7] CGColor], (id)[[UIColor kh_colorWithHexString:@"efdf1b" andAlpha:0.8] CGColor], (id)[[UIColor kh_colorWithHexString:@"f54f0A" andAlpha:0.8] CGColor],nil]; //gradient layer CALayer *gradientLayer = [CALayer layer]; CAGradientLayer *gradientLayer1 = [CAGradientLayer layer]; gradientLayer1.frame = CGRectMake(-3, -3, (self.frame.size.width + 6)/2, self.frame.size.width + 6); gradientLayer1.shadowPath=outArc.CGPath; [gradientLayer1 setColors:colors1]; [gradientLayer1 setStartPoint:CGPointMake(0.3, 0.8)]; [gradientLayer1 setEndPoint:CGPointMake(1.0, 0.0)]; [gradientLayer addSublayer:gradientLayer1]; CAGradientLayer *gradientLayer2 = [CAGradientLayer layer]; gradientLayer2.frame = CGRectMake(-3 + (self.frame.size.width + 6)/2, -3, (self.frame.size.width + 6)/2, self.frame.size.width + 6) ; gradientLayer2.shadowPath=outArc.CGPath; [gradientLayer2 setColors:colors2]; [gradientLayer2 setStartPoint:CGPointMake(0.0, 0.0)]; [gradientLayer2 setEndPoint:CGPointMake(0.8, 0.7)]; [gradientLayer addSublayer:gradientLayer2]; [gradientLayer setMask:_arcLayer]; //Use shapeLayer to intercept the gradient layer [self.layer addSublayer:gradientLayer]; UIBezierPath* aPath = [UIBezierPath bezierPath]; aPath.lineWidth = 15.0; aPath.lineCapStyle = kCGLineCapRound; //End point (start point) style aPath.lineJoinStyle = kCGLineCapRound; //Inflection point style // [aPath moveToPoint:CGPointMake(0, 0)]; //Set the starting point // [aPath addLineToPoint:CGPointMake(8, _radius-20)];//Passpoint // [aPath addLineToPoint:CGPointMake(0, _radius-20 + 15)];//Passing point // [aPath addLineToPoint:CGPointMake(-8, _radius-20)];//Passpoint [aPath moveToPoint:CGPointMake(0, 0)];//Set the starting point>>>>>>>>The point that needs to be rotated as the starting point [aPath addLineToPoint:CGPointMake(0, 5)];//Passpoint [aPath addLineToPoint:CGPointMake(-5, 0)];//Passpoint [aPath addLineToPoint:CGPointMake(0, -(_radius-15))];//Passpoint [aPath addLineToPoint:CGPointMake(5, 0)];//Passpoint [aPath addLineToPoint:CGPointMake(0, 5)];//Passpoint [aPath moveToPoint:CGPointMake(0, -(_radius-2))]; [aPath addArcWithCenter:CGPointMake(0, -(_radius-2)) radius:4 startAngle:0 endAngle:toRad(360) clockwise:YES]; [aPath closePath];//Get the last line by calling the closePath method self.pointLayer=[CAShapeLayer layer]; self.pointLayer.lineWidth=3; self.pointLayer.fillColor=[UIColor kh_colorWithHexString:@"37E5FE"].CGColor; self.pointLayer.path=aPath.CGPath; self.pointLayer.strokeColor=[UIColor clearColor].CGColor; self.pointLayer.position = CGPointMake(self.dotCenter.x, self.dotCenter.y);//Move the aPath starting point to the center point so that it can rotate normally () // shapeLayer1.anchorPoint = CGPointMake(1.0, 1.0); //Invalid UIBezierPath *dPath = [UIBezierPath bezierPath]; dPath.lineWidth = 5.0; dPath.lineCapStyle = kCGLineCapRound; //Line corner dPath.lineJoinStyle = kCGLineCapRound; //End point processing [dPath moveToPoint:CGPointMake(0, -(_radius-2))]; [dPath addArcWithCenter:CGPointMake(0, -(_radius-2)) radius:2 startAngle:0 endAngle:toRad(360) clockwise:YES]; CAShapeLayer *dPointLayer=[CAShapeLayer layer]; dPointLayer.lineWidth=3; dPointLayer.fillColor=[UIColor whiteColor].CGColor; dPointLayer.path=dPath.CGPath; dPointLayer.strokeColor=[UIColor clearColor].CGColor; [self.pointLayer addSublayer: dPointLayer]; [self.layer addSublayer: self.pointLayer]; CGFloat angle1 = toRad(-135); self.pointLayer.transform = CATransform3DMakeRotation(angle1, 0, 0, 1); UIBezierPath *bPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_dotCenter.x-5, _dotCenter.y-5, 10, 10)]; bPath.lineWidth = 5.0; bPath.lineCapStyle = kCGLineCapRound; //Line corner bPath.lineJoinStyle = kCGLineCapRound; //End point processing self.bPointLayer=[CAShapeLayer layer]; self.bPointLayer.lineWidth=3; self.bPointLayer.fillColor=[UIColor kh_colorWithHexString:@"37E5FE"].CGColor; self.bPointLayer.path=bPath.CGPath; self.bPointLayer.strokeColor=[UIColor clearColor].CGColor; [self.layer addSublayer: self.bPointLayer]; UIBezierPath *cPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_dotCenter.x-2.5, _dotCenter.y-2.5, 5, 5)]; cPath.lineWidth = 5.0; cPath.lineCapStyle = kCGLineCapRound; //Line corner cPath.lineJoinStyle = kCGLineCapRound; //End point processing CAShapeLayer *cPointLayer=[CAShapeLayer layer]; cPointLayer.lineWidth=3; cPointLayer.fillColor=[UIColor whiteColor].CGColor; cPointLayer.path=cPath.CGPath; cPointLayer.strokeColor=[UIColor clearColor].CGColor; [self.layer addSublayer: cPointLayer]; // // CGFloat angle2 = (1.8 - 1.6)/1.2*M_PI; // self.pointLayer.transform = CATransform3DMakeRotation(angle2, 0, 0, 1); // // CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; // animation.duration = 1.0f; // animation.fromValue = @(angle1); // animation.toValue = @(angle2); // animation.fillMode = kCAFillModeForwards; // animation.removedOnCompletion = NO; // animation.autoreverses = NO; // animation.timingFunction=[CAMediaTimingFunction functionWithControlPoints:0.20 :1.50 :0.20 :1.00]; // animation.repeatCount = 1; // // // CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; // // animation2.duration = 2.f; // animation2.fromValue = @(angle2 + 0.1*M_PI); // // animation2.toValue = @(angle2-0.1*M_PI); // // animation2.fillMode = kCAFillModeForwards; // // animation2.removedOnCompletion = NO; // // animation2.repeatCount = 1; // // // CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; // // animation3.duration = 2.f; // // animation3.fromValue = @(angle2-0.2*M_PI); // // animation3.toValue = @(angle2); // // animation3.fillMode = kCAFillModeForwards; // // animation3.removedOnCompletion = NO; // // animation3.repeatCount = 1; // // // // CAAnimationGroup *groupAnnimation = [CAAnimationGroup animation]; // // groupAnnimation.duration = 2.0f; // // groupAnnimation.autoreverses = NO;// Whether the animation will be executed in reverse after completion // // groupAnnimation.animations = @[animation]; // // groupAnnimation.repeatCount = 1; // [self.pointLayer addAnimation:animation forKey:@"groupAnnimation"]; // // self.pointLayer.transform = CATransform3DMakeRotation(angle2, 0, 0, 1); } -(void)setValue:(float)value{<!-- --> if(value==-1) _valueLable.text=@"--"; else _valueLable.text=[NSString stringWithFormat:@"%0.2f",value]; if(value<1.0){<!-- --> value=1.0; }else if(value>3.0){<!-- --> value=3.0f; } // if(value == _value){<!-- --> // return; // } //Subtract the minimum value and obtain the change value float valueTmp1=_value-_minValue; float valueTmp2=value-_minValue; float angle1 = toRad(-135) + valueTmp1*toRad(135); float angle2 = toRad(-135) + valueTmp2*toRad(135); self.pointLayer.transform = CATransform3DMakeRotation(angle2, 0, 0, 1); CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"transform.rotation"]; animation.mass = 10.0; //Mass, affects the spring inertia when the layer is moving. The greater the mass, the greater the extent of spring stretching and compression. animation.stiffness = 3000; //Stiffness coefficient (stiffness coefficient/elastic coefficient), the greater the stiffness coefficient, the greater the force generated by deformation, and the faster the movement animation.damping = 100.0;//Damping coefficient, the coefficient that prevents the spring from expanding and contracting. The greater the damping coefficient, the faster it stops. animation.initialVelocity = 5.f;//Initial velocity, the initial velocity of the animation view; when the velocity is a positive number, the velocity direction is consistent with the motion direction, when the velocity is a negative number, the velocity direction is opposite to the motion direction animation.duration = 1.0; animation.fromValue = @(angle1); animation.toValue = @(angle2); animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = NO; animation.autoreverses = NO; animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; animation.repeatCount = 1; [self.pointLayer addAnimation:animation forKey:@"groupAnnimation"]; UIColor *rawColor1 = [UIColor kh_colorWithHexString:@"37E5FE"]; UIColor *rawColor2 = [UIColor kh_colorWithHexString:@"60df5a"]; UIColor *rawColor3 = [UIColor kh_colorWithHexString:@"efdf1b"]; UIColor *rawColor4 = [UIColor kh_colorWithHexString:@"efdf1b"]; UIColor *rawColor5 = [UIColor kh_colorWithHexString:@"f54f0A"]; UIColor *resultColor=[UIColor kh_colorWithHexString:@"37E5FE"]; if(value<1.5){<!-- --> resultColor = [UIColor qmui_colorFromColor:rawColor1 toColor:rawColor2 progress:1-(1.5-value)]; // Key method }else if(value<2){<!-- --> resultColor = [UIColor qmui_colorFromColor:rawColor2 toColor:rawColor3 progress:1-(2.0-value)]; // Key method }else if(value<2.5){<!-- --> resultColor = [UIColor qmui_colorFromColor:rawColor3 toColor:rawColor4 progress:1-(2.5-value)]; // Key method }else if(value<=3.0){<!-- --> resultColor = [UIColor qmui_colorFromColor:rawColor4 toColor:rawColor5 progress:1-(3.0-value)]; // Key method } self.pointLayer.fillColor=resultColor.CGColor; self.bPointLayer.fillColor=resultColor.CGColor; UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-3 startAngle:_startAngle endAngle:_endAngle clockwise:YES]; _arcLayer.path=outArc.CGPath; CASpringAnimation *animation1 = [CASpringAnimation animationWithKeyPath:@"strokeEnd"]; animation1.mass = 10.0; //Mass, affects the spring inertia when the layer is moving. The greater the mass, the greater the extent of spring stretching and compression. animation1.stiffness = 3000; //Stiffness coefficient (stiffness coefficient/elastic coefficient), the greater the stiffness coefficient, the greater the force generated by deformation, and the faster the movement animation1.damping = 100.0;//Damping coefficient, the coefficient that prevents the spring from expanding and contracting. The greater the damping coefficient, the faster it stops. animation1.initialVelocity = 5.f;//Initial velocity, the initial velocity of the animation view; when the velocity is a positive number, the velocity direction is consistent with the motion direction, when the velocity is a negative number, the velocity direction is opposite to the motion direction animation1.duration = 1.0; animation1.fromValue = @((_value-_minValue)/(_maxValue-_minValue)); animation1.toValue = @((value-_minValue)/(_maxValue-_minValue)); animation1.fillMode = kCAFillModeForwards; animation1.removedOnCompletion = NO; animation1.autoreverses = NO; animation1.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; animation1.repeatCount = 1; [_arcLayer addAnimation:animation1 forKey:@"groupAnnimation"]; _value=value; } @end
use
float w=200; WBBDashBoard *dashBoard=[[WBBDashBoard alloc]initWithFrame:CGRectMake(0,10, w, w)]; dashBoard.center= contextView.center; [contextView addSubview:dashBoard];
I also have the Android version: