Перекрывающийся круг погрузчика

Я пытался воспроизвести перекрывающийся круг, который Apple сделала для приложения «Активность». (См. изображение ниже).

введите здесь описание изображения

Если вы используете стандартный путь Безье, начальное/конечное положение будет иметь значение только между 0 и 2PI. Если вы попытаетесь, например, заполнить для 4PI (даже с использованием некоторых теней), вы не сможете смоделировать перекрывающуюся нагрузку.

Как можно сделать что-то похожее на решение Apple для создания перекрывающегося круга?


person GrizzlyBear    schedule 24.07.2015    source источник
comment
Если вы используете стандартный путь Безье, начальное/конечное положение будет иметь эффект только между 0 и 2PI. Но тогда вы можете применить преобразование вращения, поэтому фактически вы можете начинать и заканчивать где угодно.   -  person matt    schedule 24.07.2015
comment
Вы уверены, что это не растровое изображение? А если нет, то уверены ли вы, что каждый круг состоит из одного пути?   -  person Krumelur    schedule 24.07.2015
comment
Нет, на самом деле я не знаю, как это реализовано, но если вы видите анимацию, то кажется, что это только один путь!   -  person GrizzlyBear    schedule 24.07.2015
comment
Пожалуйста, Мэтт, не мог бы ты быть более ясным? Когда вы применяете преобразование? Анимация кажется плавной, без двух шагов. Может быть, я не понял, о чем вы говорите...   -  person GrizzlyBear    schedule 24.07.2015


Ответы (2)


Это показывает общую идею — преобразование — это определенно правильный путь. Вам придется настроить размеры, стрелки и тени так, как вам нужно. Но вид позволяет настроить оттенок и угол из 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.

person picciano    schedule 24.07.2015
comment
Это кажется отличным решением! Как оживить круг? Достаточно просто изменить угол поворота? - person GrizzlyBear; 25.07.2015
comment
Вы можете напрямую изменить угол, и вид обновится. Для чего-то более сложного вы можете сделать это свойство анимируемым, чтобы изменение значения анимировало вращение. Вам нужно будет реализовать собственный подкласс CALayer и реализовать needsDisplayForKey: initWithLayer: drawInContext: и actionForKey: - person picciano; 25.07.2015

Вот моя попытка:

введите здесь описание изображения

Он создается полностью в коде (мне пришлось использовать Слой градиента угла, чтобы получить градиент, но остальные кода мой). Я могу легко получить любой желаемый угол, просто применив преобразование вращения:

введите здесь описание изображения

Конечно, я также жестко закодировал все значения, потому что я просто пробовал это, но не стесняйтесь играть с этим:

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))
person matt    schedule 24.07.2015