使用核心图形绘制浮雕弧

时间:2012-05-02 16:36:54

标签: iphone ios core-graphics

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

enter image description here

到目前为止我所做的事情看起来像这样

enter image description here

请帮助我画出具有这种效果的弧线。我的代码如下所示,我正在做的是使用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];
}

2 个答案:

答案 0 :(得分:23)

除非您要动态更改形状,否则您最好只在图像编辑器中创建图像。我知道在Photoshop,Illustrator或Fireworks中创建该效果很容易。

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

  1. 剪裁到形状(使用例如CGContextClipCGContextClipToMask)。
  2. 制作形状的所有内容的路径或掩码。
  3. 设置阴影参数(使用CGContextSetShadowWithColor)。
  4. 填充步骤2中的路径或蒙版。这会在形状内部投射阴影,并且只绘制阴影,因为您在步骤1中剪切了形状。
  5. 如果你正确地做了所有这些,你可以得到一个很好的结果:

    screen shot of arc with inner shadow

    这是我写的用来绘制的代码。我在自定义视图子类的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中的点。碰巧(虽然我认为没有记录)CGContextCreateCopyByStrokingPathCGPathAddRect在相反的方向上绘制。因此,如果我复制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 heremy 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;
}

你也需要拇指图像 enter image description here