重叠装载机圆

Overlapping loader circle

我试图重现 Apple 为应用程序制作的重叠圆 "Activity"。 (见下图)。

如果您使用标准贝塞尔曲线路径,staring/ending 位置将仅在 0 到 2PI 之间产生影响。例如,如果您尝试填充 4PI(即使使用一些阴影),您将无法模拟重叠加载。

如何制作类似于 Apple 解决方案来创建重叠圆圈?

这是我的尝试:

它完全是用代码生成的(我不得不使用 Angle Gradient Layer 来获取渐变,但其余代码是我的)。只需应用旋转变换,我就可以轻松获得任何所需的角度:

当然我也对所有值进行了硬编码,因为我只是尝试一下,但请随意使用:

let con = UIGraphicsGetCurrentContext()
let c1 = UIColor(red: 0.8, green: 0.8, blue: 0.7, alpha: 1).CGColor
let c2 = UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1).CGColor
let outer:CGFloat = 20
let diff:CGFloat = 20
let inner:CGFloat = outer + diff
func circle(inset:CGFloat) {
    CGContextAddEllipseInRect(con, rect.rectByInsetting(
        dx: inset, dy: inset))
}

CGContextSaveGState(con)
circle(outer); circle(inner); CGContextEOClip(con)
let im = AngleGradientLayer.newImageGradientInRect(
    rect, colors:[c1,c2], locations:[0,1]).takeRetainedValue()
CGContextDrawImage(con, rect, im)
CGContextRestoreGState(con)

circle(outer)
CGContextStrokePath(con)
circle(inner)
CGContextStrokePath(con)

let (_,lower) = rect.rectsByDividing(rect.height / 2, fromEdge: .MinYEdge)
CGContextAddRect(con, lower)
CGContextEOClip(con)
CGContextSetFillColorWithColor(con, c1)

CGContextSaveGState(con)
CGContextSetShadowWithColor(con, CGSizeMake(0,3), 6, 
    UIColor.blackColor().colorWithAlphaComponent(0.7).CGColor)
let top = lower.origin.y - diff/2
let left = rect.width - diff * 2
circle(outer); circle(inner); CGContextEOClip(con)
CGContextFillEllipseInRect(con, CGRectMake(left,top,diff,diff))
CGContextRestoreGState(con)

CGContextStrokeEllipseInRect(con, CGRectMake(left,top,diff,diff))

这表明了总体思路——转换绝对是必经之路。您必须根据需要调整大小、箭头和阴影。但是视图允许从 Interface Builder 调整色调和角度。这应该让你继续。

#import <UIKit/UIKit.h>
IB_DESIGNABLE

@interface ActivityCircleView : UIView

@property (nonatomic) IBInspectable CGFloat angleOfRotationInDegrees;

@end


#import "ActivityCircleView.h"

@implementation ActivityCircleView

- (void)setAngleOfRotation:(CGFloat)angleOfRotationInDegrees {
    _angleOfRotationInDegrees = angleOfRotationInDegrees;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    [self drawActivityWithTintColor:self.tintColor angleOfRotationInDegrees:self.angleOfRotationInDegrees];
}

- (void)drawActivityWithTintColor: (UIColor*)tintColor angleOfRotationInDegrees: (CGFloat)angleOfRotationInDegrees
{
    //// General Declarations
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Color Declarations
    CGFloat tintColorRGBA[4];
    [tintColor getRed: &tintColorRGBA[0] green: &tintColorRGBA[1] blue: &tintColorRGBA[2] alpha: &tintColorRGBA[3]];

    UIColor* brighter = [UIColor colorWithRed: (tintColorRGBA[0] * 0.5 + 0.5) green: (tintColorRGBA[1] * 0.5 + 0.5) blue: (tintColorRGBA[2] * 0.5 + 0.5) alpha: (tintColorRGBA[3] * 0.5 + 0.5)];

    //// Gradient Declarations
    CGFloat gradientLocations[] = {0, 0.73};
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)@[(id)tintColor.CGColor, (id)brighter.CGColor], gradientLocations);

    //// Shadow Declarations
    NSShadow* shadow = [[NSShadow alloc] init];
    [shadow setShadowColor: UIColor.blackColor];
    [shadow setShadowOffset: CGSizeMake(0.1, 0.1)];
    [shadow setShadowBlurRadius: 5];

    //// Activity Circle
    {
        //// Circle With Overlapping Shadow
        {
            CGContextSaveGState(context);
            CGContextTranslateCTM(context, 50, 50);
            CGContextRotateCTM(context, -angleOfRotationInDegrees * M_PI / 180);



            //// Left Half Circle Drawing
            UIBezierPath* leftHalfCirclePath = UIBezierPath.bezierPath;
            [leftHalfCirclePath moveToPoint: CGPointMake(0, -40)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(0, -20) controlPoint1: CGPointMake(0, -40) controlPoint2: CGPointMake(0, -31.68)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(-7.44, -18.57) controlPoint1: CGPointMake(-2.63, -20) controlPoint2: CGPointMake(-5.14, -19.49)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(-20, -0) controlPoint1: CGPointMake(-14.8, -15.62) controlPoint2: CGPointMake(-20, -8.42)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(0, 20) controlPoint1: CGPointMake(-20, 11.05) controlPoint2: CGPointMake(-11.05, 20)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(0, 40) controlPoint1: CGPointMake(0, 27.41) controlPoint2: CGPointMake(0, 34.35)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(-40, -0) controlPoint1: CGPointMake(-22.09, 40) controlPoint2: CGPointMake(-40, 22.09)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(-24.08, -31.94) controlPoint1: CGPointMake(-40, -13.05) controlPoint2: CGPointMake(-33.75, -24.64)];
            [leftHalfCirclePath addLineToPoint: CGPointMake(-23.84, -32.13)];
            [leftHalfCirclePath addCurveToPoint: CGPointMake(0, -40) controlPoint1: CGPointMake(-17.18, -37.07) controlPoint2: CGPointMake(-8.93, -40)];
            [leftHalfCirclePath addLineToPoint: CGPointMake(0, -40)];
            [leftHalfCirclePath closePath];
            [tintColor setFill];
            [leftHalfCirclePath fill];


            //// Circle With Shadow Drawing
            UIBezierPath* circleWithShadowPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(-10, 20, 20, 20)];
            CGContextSaveGState(context);
            CGContextSetShadowWithColor(context, shadow.shadowOffset, shadow.shadowBlurRadius, [shadow.shadowColor CGColor]);
            [brighter setFill];
            [circleWithShadowPath fill];
            CGContextRestoreGState(context);



            //// Right Half Circle Drawing
            UIBezierPath* rightHalfCirclePath = UIBezierPath.bezierPath;
            [rightHalfCirclePath moveToPoint: CGPointMake(40, -0)];
            [rightHalfCirclePath addCurveToPoint: CGPointMake(0, 40) controlPoint1: CGPointMake(40, 22.09) controlPoint2: CGPointMake(22.09, 40)];
            [rightHalfCirclePath addCurveToPoint: CGPointMake(0, 20) controlPoint1: CGPointMake(0, 33.83) controlPoint2: CGPointMake(0, 27.02)];
            [rightHalfCirclePath addCurveToPoint: CGPointMake(20, -0) controlPoint1: CGPointMake(11.05, 20) controlPoint2: CGPointMake(20, 11.05)];
            [rightHalfCirclePath addCurveToPoint: CGPointMake(0, -20) controlPoint1: CGPointMake(20, -11.05) controlPoint2: CGPointMake(11.05, -20)];
            [rightHalfCirclePath addCurveToPoint: CGPointMake(0, -40) controlPoint1: CGPointMake(0, -28.3) controlPoint2: CGPointMake(0, -35.35)];
            [rightHalfCirclePath addCurveToPoint: CGPointMake(40, -0) controlPoint1: CGPointMake(22.09, -40) controlPoint2: CGPointMake(40, -22.09)];
            [rightHalfCirclePath closePath];
            CGContextSaveGState(context);
            [rightHalfCirclePath addClip];
            CGContextDrawLinearGradient(context, gradient, CGPointMake(20, -40), CGPointMake(20, 40), 0);
            CGContextRestoreGState(context);



            CGContextRestoreGState(context);
        }


        //// Arrow
        {
            //// Arrow Line Drawing
            UIBezierPath* arrowLinePath = UIBezierPath.bezierPath;
            [arrowLinePath moveToPoint: CGPointMake(43.5, 20.5)];
            [arrowLinePath addLineToPoint: CGPointMake(57.5, 20.5)];
            arrowLinePath.lineCapStyle = kCGLineCapRound;

            [UIColor.blackColor setStroke];
            arrowLinePath.lineWidth = 4;
            [arrowLinePath stroke];


            //// Arrow Head Drawing
            UIBezierPath* arrowHeadPath = UIBezierPath.bezierPath;
            [arrowHeadPath moveToPoint: CGPointMake(50.8, 14.5)];
            [arrowHeadPath addLineToPoint: CGPointMake(57.5, 20.5)];
            [arrowHeadPath addLineToPoint: CGPointMake(50.8, 26.5)];
            arrowHeadPath.lineCapStyle = kCGLineCapRound;

            [UIColor.blackColor setStroke];
            arrowHeadPath.lineWidth = 4;
            [arrowHeadPath stroke];
        }
    }


    //// Cleanup
    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorSpace);
}


@end

顺便说一句,我作弊并使用了PaintCode。