我正在尝试实现自定义滑块,如下图所示。
到目前为止我所做的事情看起来像这样
请帮助我画出具有这种效果的弧线。我的代码如下所示,我正在做的是使用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];
}
答案 0 :(得分:23)
除非您要动态更改形状,否则您最好只在图像编辑器中创建图像。我知道在Photoshop,Illustrator或Fireworks中创建该效果很容易。
也就是说,使用Core Graphics绘制内部阴影需要几个步骤:
CGContextClip
或CGContextClipToMask
)。CGContextSetShadowWithColor
)。如果你正确地做了所有这些,你可以得到一个很好的结果:
这是我写的用来绘制的代码。我在自定义视图子类的drawRect:
中编写了它,但您可以轻松地使用此代码绘制到任何图形上下文中。
- (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];
接下来,我要求Core Graphics制作一条新路径,即arc
路径的轮廓。请注意我如何要求它的笔画宽度为kArcThickness
和圆线上限:
CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
我还需要该路径的反转:包含的每个点的路径,除了 shape
中的点。碰巧(虽然我认为没有记录)CGContextCreateCopyByStrokingPath
和CGPathAddRect
在相反的方向上绘制。因此,如果我复制shape
并在其周围绘制一个巨大的矩形,则非零缠绕规则意味着新路径将与shape
相反:
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); {
第1步:剪辑到形状:
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextClip(gc);
第2步:嗯,我在创建shapeInverse
时已经执行了此步骤。
第3步:我设置了阴影参数:
CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
步骤4:我填写第2步的反转形状:
CGContextBeginPath(gc);
CGContextAddPath(gc, shapeInverse);
CGContextFillPath(gc);
现在我恢复图形状态,专门恢复剪切路径并取消设置阴影参数。
} CGContextRestoreGState(gc);
最后,我会用浅灰色抚摸shape
以使边缘更加清晰:
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);
}
对于更复杂的形状,您可以查看my answer here和my answer here。
以下是所有代码,以便于复制:
- (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);
}
答案 1 :(得分:2)
检查以下代码以获取上述组件的完整功能版本,其工作情况(可能有点乱)。
#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;
}
你也需要拇指图像