使用核心graphics绘制浮雕圆弧

我正在试图实现一个自定义滑块,如下图所示。

在这里输入图像说明

到目前为止,我所做的是这样的

在这里输入图像说明

请帮我画出这种效果的弧线。 我的代码如下,我正在做的是使用CGContextAddArc与线宽kLineWidth绘制弧线。

- (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext: (CGContextRef)context { UIGraphicsPushContext(context); CGContextBeginPath(context); CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y); CGImageRef imageRef = [UIImage imageNamed:@"circle25.png"].CGImage; CGRect rect = CGRectMake(sliderButtonCenterPoint.x - kThumbRadius, sliderButtonCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2); CGContextDrawImage(context, rect, imageRef); //CGContextAddArc(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y, kThumbRadius, 0.0, 2*M_PI, NO); CGContextFillPath(context); UIGraphicsPopContext(); } - (CGPoint)drawArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius inContext:(CGContextRef)context { UIGraphicsPushContext(context); CGContextBeginPath(context); float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, M_PI/3);// 2*M_PI CGFloat startAngle = (4*M_PI)/3; CGFloat endAngle = startAngle + angleFromTrack; CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, NO); CGPoint arcEndPoint = CGContextGetPathCurrentPoint(context); CGContextStrokePath(context); UIGraphicsPopContext(); return arcEndPoint; } - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGPoint middlePoint; middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2; middlePoint.y = self.bounds.origin.y + self.bounds.size.width; CGContextSetLineWidth(context, kLineWidth); CGFloat radius = [self sliderRadius]; [self.maximumTrackTintColor setStroke]; [self drawArcTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context]; [self.minimumTrackTintColor setStroke]; self.thumbCenterPoint = [self drawArcTrack:self.value atPoint:middlePoint withRadius:radius inContext:context]; [self.thumbTintColor setFill]; [self drawThumbAtPoint:self.thumbCenterPoint inContext:context]; } 

除非你要dynamic改变形状,否则在图像编辑器中创build图像可能会更好。 我知道在Photoshop,Illustrator或Fireworks中创build效果很容易。

也就是说,使用Core Graphics绘制内部阴影需要几个步骤:

  1. 剪切到形状(使用例如CGContextClipCGContextClipToMask )。
  2. 做一个道路或面具的一切, 形状。
  3. 设置你的影子参数(使用CGContextSetShadowWithColor )。
  4. 填充步骤2中的path或遮罩。这会在形状内投射阴影,并且由于您在步骤1中裁剪了该形状,因此只会绘制阴影。

如果你正确地做到了这一点,你可以得到这样一个很好的结果:

与内在阴影的弧的屏幕快照

这是我写的代码来绘制。 我把它写在一个自定义视图子类的drawRect:中,但是你可以很容易地使用这个代码来绘制任何graphics上下文。

 - (void)drawRect:(CGRect)rect { CGContextRef gc = UIGraphicsGetCurrentContext(); 

首先,我创build一个只是一个弧的path:

  static CGFloat const kArcThickness = 20.0f; CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f); CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds)); CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness); UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO]; 

接下来,我要求Core Graphics制作一个新的path,即arcpath的轮廓。 请注意我是如何要求它的kArcThickness和圆线上限的中风宽度:

  CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f); 

我也需要这个path的反转:一个包含除了 shape之外的每一个点的path。 它恰好(虽然我不认为它被logging) CGContextCreateCopyByStrokingPathCGPathAddRect绘制在相反的方向。 所以如果我复制shape并在其周围绘制一个巨大的矩形,那么非零缠绕规则意味着新的path将是shape的逆向:

  CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape); CGPathAddRect(shapeInverse, NULL, CGRectInfinite); 

现在我可以开始绘画了。 首先,我会用浅灰色填充形状:

  CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor); CGContextFillPath(gc); 

接下来我实际执行上面列出的四个步骤。 我必须保存graphics状态,以便在完成后撤消剪切和阴影参数。

  CGContextSaveGState(gc); { 

第一步:剪裁形状:

  CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextClip(gc); 

第2步:嗯,我已经做了这一步,当我创buildshapeInverse

第3步:我设置阴影参数:

  CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor); 

第四步:填写第二步的反面形状:

  CGContextBeginPath(gc); CGContextAddPath(gc, shapeInverse); CGContextFillPath(gc); 

现在我恢复graphics状态,它专门恢复剪切path并取消阴影参数。

  } CGContextRestoreGState(gc); 

最后,我会用浅灰色描边来保持边缘的清晰:

  CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor); CGContextSetLineWidth(gc, 1); CGContextSetLineJoin(gc, kCGLineCapRound); CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextStrokePath(gc); 

当然,我完成清理工作

  CGPathRelease(shape); CGPathRelease(shapeInverse); } 

对于更复杂的形状,你可以在这里看我的答案 , 我的答案在这里 。

以下是所有代码,以便于复制:

 - (void)drawRect:(CGRect)rect { CGContextRef gc = UIGraphicsGetCurrentContext(); static CGFloat const kArcThickness = 20.0f; CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f); CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds)); CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness); UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO]; CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f); CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape); CGPathAddRect(shapeInverse, NULL, CGRectInfinite); CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor); CGContextFillPath(gc); CGContextSaveGState(gc); { CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextClip(gc); CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor); CGContextBeginPath(gc); CGContextAddPath(gc, shapeInverse); CGContextFillPath(gc); } CGContextRestoreGState(gc); CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor); CGContextSetLineWidth(gc, 1); CGContextSetLineJoin(gc, kCGLineCapRound); CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextStrokePath(gc); CGPathRelease(shape); CGPathRelease(shapeInverse); } 

检查下面的代码为上述组件的function完整的版本,其工作(可能是它的一点凌乱)。

 #import "UIArcSlider.h" @interface UIArcSlider() @property (nonatomic) CGPoint thumbCenterPoint; #pragma mark - Init and Setup methods - (void)setup; #pragma mark - Thumb management methods - (BOOL)isPointInThumb:(CGPoint)point; #pragma mark - Drawing methods - (CGFloat)sliderRadius; - (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context; - (CGPoint)drawTheArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius AndColor:(Boolean) isWhite inContext:(CGContextRef)context; - (CGPoint)drawTheLineTrack:(float)track withColor:(Boolean) isWhite inContext:(CGContextRef)context; @end #pragma mark - @implementation UIArcSlider @synthesize sliderStyle = _sliderStyle; - (void)setSliderStyle:(UISliderStyle)sliderStyle { if (sliderStyle != _sliderStyle) { _sliderStyle = sliderStyle; [self setNeedsDisplay]; } } @synthesize value = _value; - (void)setValue:(float)value { if (value != _value) { if (value > self.maximumValue) { value = self.maximumValue; } if (value < self.minimumValue) { value = self.minimumValue; } _value = value; [self setNeedsDisplay]; [self sendActionsForControlEvents:UIControlEventValueChanged]; } } @synthesize minimumValue = _minimumValue; - (void)setMinimumValue:(float)minimumValue { if (minimumValue != _minimumValue) { _minimumValue = minimumValue; if (self.maximumValue < self.minimumValue) { self.maximumValue = self.minimumValue; } if (self.value < self.minimumValue) { self.value = self.minimumValue; } } } @synthesize maximumValue = _maximumValue; - (void)setMaximumValue:(float)maximumValue { if (maximumValue != _maximumValue) { _maximumValue = maximumValue; if (self.minimumValue > self.maximumValue) { self.minimumValue = self.maximumValue; } if (self.value > self.maximumValue) { self.value = self.maximumValue; } } } @synthesize thumbCenterPoint = _thumbCenterPoint; /** @name Init and Setup methods */ #pragma mark - Init and Setup methods - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } - (void)awakeFromNib { [self setup]; } - (void)setup { self.value = 0.0; self.minimumValue = 0.0; self.maximumValue = 100.0; self.thumbCenterPoint = CGPointZero; UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHappened:)]; [self addGestureRecognizer:tapGestureRecognizer]; UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureHappened:)]; panGestureRecognizer.maximumNumberOfTouches = panGestureRecognizer.minimumNumberOfTouches; [self addGestureRecognizer:panGestureRecognizer]; } /** @name Drawing methods */ #pragma mark - Drawing methods #define kLineWidth 22.0 #define kThumbRadius 20.0 #define kPadding 20.0 - (CGFloat)sliderRadius { CGFloat radius = self.bounds.size.width; radius -= MAX(kLineWidth + kPadding, kThumbRadius + kPadding); return radius; } -(CGFloat)sliderWidth { return self.bounds.size.width - kThumbRadius*2; } - (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context { UIGraphicsPushContext(context); CGContextBeginPath(context); CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y); CGRect rect = CGRectMake(sliderButtonCenterPoint.x - kThumbRadius, sliderButtonCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2); CGImageRef imageRef = [UIImage imageNamed:@"circle25.png"].CGImage; CGContextDrawImage(context, rect, imageRef); UIGraphicsPopContext(); } - (CGPoint)drawTheArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius AndColor:(Boolean) isWhite inContext:(CGContextRef)context { static CGFloat const kArcThickness = kLineWidth; CGPoint arcCenter = center; CGFloat arcRadius = radius; float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, M_PI/3);// 2*M_PI CGFloat startAngle = (4*M_PI)/3; CGFloat endAngle = startAngle + angleFromTrack; UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:startAngle endAngle:endAngle clockwise:YES]; CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f); CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape); CGPathAddRect(shapeInverse, NULL, CGRectInfinite); CGPoint arcEndPoint = [arc currentPoint]; CGContextBeginPath(context); CGContextAddPath(context, shape); if (isWhite) { CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:.9 alpha:1].CGColor); } else {//70,172, 220 CGContextSetFillColorWithColor(context, [UIColor colorWithRed:70/255.0 green:172/255.0 blue:220/255.0 alpha:1].CGColor); } CGContextFillPath(context); CGContextSaveGState(context); { CGContextBeginPath(context); CGContextAddPath(context, shape); CGContextClip(context); if (isWhite) { CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor); } else { CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithRed:85/255.0 green:183/255.0 blue:230/255.0 alpha:.25].CGColor); } CGContextBeginPath(context); CGContextAddPath(context, shapeInverse); CGContextFillPath(context); } CGContextRestoreGState(context); if (isWhite) { CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:.75 alpha:1].CGColor); } else { CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:62/255.0 green:135/255.0 blue:169/255.0 alpha:1].CGColor); } CGContextSetLineWidth(context, 1); CGContextSetLineJoin(context, kCGLineCapRound); CGContextBeginPath(context); CGContextAddPath(context, shape); CGContextStrokePath(context); CGPathRelease(shape); CGPathRelease(shapeInverse); return arcEndPoint; } - (CGPoint)drawTheLineTrack:(float)track withColor:(Boolean) isWhite inContext:(CGContextRef)context { CGFloat sliderWidth = [self sliderWidth]; CGFloat xStart = self.bounds.origin.x + (self.bounds.size.width - sliderWidth)/2; CGFloat lineRectX = (self.bounds.size.width - sliderWidth)/2; CGFloat lineThickness = kLineWidth/2; CGFloat lineRectY = (self.bounds.size.height - lineThickness)/2; CGFloat xPoint = translateValueToPoint(track, xStart, self.maximumValue, sliderWidth); sliderWidth = xPoint; CGRect lineRect = CGRectMake(lineRectX, lineRectY, sliderWidth, lineThickness); UIBezierPath *line = [UIBezierPath bezierPathWithRoundedRect:lineRect cornerRadius:lineThickness/2]; CGPathRef shape = CGPathCreateCopyByStrokingPath(line.CGPath, NULL, lineThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f); CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape); CGPathAddRect(shapeInverse, NULL, CGRectInfinite); CGPoint arcEndPoint = CGPointMake(lineRectX + xPoint, lineRectY + (lineThickness/2)); CGContextBeginPath(context); CGContextAddPath(context, shape); if (isWhite) { CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:.9 alpha:1].CGColor); } else {//70,172, 220 CGContextSetFillColorWithColor(context, [UIColor colorWithRed:70/255.0 green:172/255.0 blue:220/255.0 alpha:1].CGColor); } CGContextFillPath(context); CGContextSaveGState(context); { CGContextBeginPath(context); CGContextAddPath(context, shape); CGContextClip(context); if (isWhite) { CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor); } else { CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithRed:85/255.0 green:183/255.0 blue:230/255.0 alpha:.25].CGColor); } CGContextBeginPath(context); CGContextAddPath(context, shapeInverse); CGContextFillPath(context); } CGContextRestoreGState(context); if (isWhite) { CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:.75 alpha:1].CGColor); } else { CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:62/255.0 green:135/255.0 blue:169/255.0 alpha:1].CGColor); } CGRect outlineRect = CGRectMake(lineRectX - (lineThickness/2), lineRectY - (lineThickness/2), sliderWidth + lineThickness, lineThickness * 2); UIBezierPath *outline = [UIBezierPath bezierPathWithRoundedRect:outlineRect cornerRadius:lineThickness]; CGContextSetLineWidth(context, 1); CGContextSetLineJoin(context, kCGLineCapSquare); CGContextBeginPath(context); CGContextAddPath(context, outline.CGPath); CGContextStrokePath(context); CGPathRelease(shape); CGPathRelease(shapeInverse); return arcEndPoint; } - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGPoint middlePoint; switch (self.sliderStyle) { case UISliderStyleLine: [self drawTheLineTrack:self.maximumValue withColor:YES inContext:context]; self.thumbCenterPoint = [self drawTheLineTrack:self.value withColor:NO inContext:context]; [self drawThumbAtPoint:self.thumbCenterPoint inContext:context]; break; case UISliderStyleArc: default: middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2; middlePoint.y = self.bounds.origin.y + self.bounds.size.width; CGFloat radius = [self sliderRadius]; [self drawTheArcTrack:self.maximumValue atPoint:middlePoint withRadius:radius AndColor:YES inContext:context]; self.thumbCenterPoint = [self drawTheArcTrack:self.value atPoint:middlePoint withRadius:radius AndColor:NO inContext:context]; [self drawThumbAtPoint:self.thumbCenterPoint inContext:context]; break; } } /** @name Thumb management methods */ #pragma mark - Thumb management methods - (BOOL)isPointInThumb:(CGPoint)point { CGRect thumbTouchRect = CGRectMake(self.thumbCenterPoint.x - kThumbRadius, self.thumbCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2); return CGRectContainsPoint(thumbTouchRect, point); } /** @name UIGestureRecognizer management methods */ #pragma mark - UIGestureRecognizer management methods - (void)panGestureHappened:(UIPanGestureRecognizer *)panGestureRecognizer { CGPoint tapLocation = [panGestureRecognizer locationInView:self]; switch (panGestureRecognizer.state) { case UIGestureRecognizerStateChanged: { CGFloat radius; CGPoint sliderCenter; CGPoint sliderStartPoint; CGFloat angle; CGFloat xStart; CGFloat maximumValue; CGFloat sliderWidth; CGFloat point; switch (self.sliderStyle) { case UISliderStyleLine: sliderWidth = [self sliderWidth]; xStart = self.bounds.origin.x + (self.bounds.size.width - sliderWidth)/2; maximumValue = self.maximumValue; point = tapLocation.x; self.value = translatePointToValue(point, xStart, maximumValue, sliderWidth); break; case UISliderStyleArc: default: radius = [self sliderRadius]; sliderCenter = CGPointMake(self.bounds.size.width/2, self.bounds.size.width); sliderStartPoint = CGPointMake(sliderCenter.x - (radius * 1.15), sliderCenter.y - (radius * 2.1) ); angle = angleBetweenThreePoints(sliderCenter, sliderStartPoint, tapLocation); if (angle < 0) { angle = -angle; } else { angle = 0.0; //angle = M_PI/3 - angle; } self.value = translateValueFromSourceIntervalToDestinationInterval(angle, 0, M_PI/3, self.minimumValue, self.maximumValue); break; } } default: break; } } - (void)tapGestureHappened:(UITapGestureRecognizer *)tapGestureRecognizer { if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) { CGPoint tapLocation = [tapGestureRecognizer locationInView:self]; if ([self isPointInThumb:tapLocation]) { // do something on tap } else { } } } @end /** @name Utility Functions */ #pragma mark - Utility Functions float translateValueFromSourceIntervalToDestinationInterval(float sourceValue, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum) { float a, b, destinationValue; a = (destinationIntervalMaximum - destinationIntervalMinimum) / (sourceIntervalMaximum - sourceIntervalMinimum); b = destinationIntervalMaximum - a*sourceIntervalMaximum; destinationValue = a*sourceValue + b; return destinationValue; } float translateValueToPoint(float value, float xStart, float maximum, float width) { float point = (width * value) / maximum; // // point = xStart + point; // return point; } float translatePointToValue(float point, float xStart, float maximum, float width) { float value = (point) * maximum; value = value / width; return value; } CGFloat angleBetweenThreePoints(CGPoint centerPoint, CGPoint p1, CGPoint p2) { CGPoint v1 = CGPointMake(p1.x - centerPoint.x, p1.y - centerPoint.y); CGPoint v2 = CGPointMake(p2.x - centerPoint.x, p2.y - centerPoint.y); CGFloat angle = atan2f(v2.x*v1.y - v1.x*v2.y, v1.x*v2.x + v1.y*v2.y); return angle; } 

您还需要拇指图像 在这里输入图像说明