Разрезание изображения на содержимое CALayers во время анимации

Я создал подкласс CALayer для рисования фрагмента круговой диаграммы с пользовательским цветом (color), начальным углом (actualStartAngle), конечным углом (actualEndAngle) и изображением (image)[Это изображение всегда должно быть в середине the slice], так как анимация этого невозможна с помощью обычного CAShapeLayer. Слой в настоящее время рисуется следующим образом:

- (void)drawInContext:(CGContextRef)ctx{
    CGContextBeginPath(ctx);

    CGContextMoveToPoint(ctx, self.center.x, self.center.y);
    CGContextAddArc(ctx, self.center.x, self.center.y, self.radius, self.actualEndAngle, self.actualStartAngle, YES);

    CGContextClosePath(ctx);

    CGContextSetFillColorWithColor(ctx, self.color);
    CGContextSetLineWidth(ctx, 0);

    CGContextDrawPath(ctx, kCGPathFillStroke);

    if (self.image) {
        CGContextDrawImage(ctx, [self getFrameForImgLayerWithStartAngle:self.actualStartAngle andEndAngle:self.actualEndAngle], self.image);
    }
}

Эти отдельные фрагменты анимируются:

[CATransaction begin];

CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:@"color"];
colorAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
colorAnimation.duration = duration;
colorAnimation.fromValue = (id)self.color;
colorAnimation.toValue = (id)colorTU.CGColor;

CABasicAnimation *startAngleAnimation = [CABasicAnimation animationWithKeyPath:@"actualStartAngle"];
startAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
startAngleAnimation.duration = duration;
startAngleAnimation.fromValue = [self valueForKey:@"actualStartAngle"];
startAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newStartAngle];

CABasicAnimation *endAngleAnimation = [CABasicAnimation animationWithKeyPath:@"actualEndAngle"];
endAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
endAngleAnimation.duration = duration;
endAngleAnimation.fromValue = [self valueForKey:@"actualEndAngle"];
endAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newEndAngle];

if (completionBlock) {
    [CATransaction setCompletionBlock:completionBlock];
}

if (colorTU && colorTU.CGColor != self.color) {
    [self addAnimation:colorAnimation forKey:@"color"];
}
if (newStartAngle != self.actualStartAngle) {
    [self addAnimation:startAngleAnimation forKey:@"actualStartAngle"];
}
if (newEndAngle != self.actualEndAngle) {
    [self addAnimation:endAngleAnimation forKey:@"actualEndAngle"];
}

[CATransaction commit];

Проблема в том, что я хотел бы, чтобы изображение занимало только то пространство, которое занимает отдельный слой (соответственно, форма в нем). Это решается в общем таким методом:

- (BOOL)imageFitsIntoSliceWithStartAngle:(float)sA andEndAngle:(float)eA{
    BOOL fits = YES;
    CGRect rectToUse = [self getFrameForImgLayerWithStartAngle:sA andEndAngle:eA];

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:self.center];
    [path addArcWithCenter:self.center radius:self.deg startAngle:sA endAngle:eA clockwise:YES];
    for (int w = 0; w <= 1; w++) {
        if (fits) {
            for (int h = 0; h <= 1; h++) {
                if (![path containsPoint:CGPointMake(rectToUse.origin.x + rectToUse.size.width * w, rectToUse.origin.y + rectToUse.size.height * h)]) {
                    fits = NO;
                    break;
                }
            }
        }
    }
    return fits;
}

Проблема сейчас в том, что он может поместиться в конце, а не в начале анимации, поэтому мне пришлось бы обрезать изображение, чтобы оно было таким же большим, как фрагмент, когда это произойдет.

Я пробовал следующее:

  • установите маску для среза вместо того, чтобы рисовать его напрямую -> Это, во-первых, плохое кодирование, и я обнаружил, что оно очень неэффективно с точки зрения энергии, а также создало для меня ошибку (expecting model layer not copy).

Любые идеи о том, как решить эту проблему? (не то, что я пытался, а эта проблема в целом, я не думаю, что вы должны делать что-то вроде того, что я пробовал выше)

Спасибо


person user1710004    schedule 28.03.2015    source источник
comment
Извините, у меня нет времени копаться в вашем коде, но вы пробовали CGContextClipToMask?   -  person Kujey    schedule 29.03.2015
comment
Насколько я понимаю, CGContextClipToMask, даже когда он будет работать, очень неэффективен. Я в основном «решил» эту проблему, применив маску к отдельному imageLayer, но это действительно неэффективный метод и вызывает адскую задержку. Нарисовать изображение было бы тем более, верно?   -  person user1710004    schedule 29.03.2015
comment
вау, какой размер у твоего изображения?   -  person Kujey    schedule 29.03.2015
comment
Ну, мне пришлось бы перерисовывать его с каждым кадром, и он составляет около 400x400 пикселей, поэтому на Retina (соотв. Retina HD на 6 Plus с @3x) это 800 соотв. 1200 пикселей работает при 30 кадрах в секунду мин. казаться гладким...   -  person user1710004    schedule 29.03.2015
comment
Ох, хорошо. Кажется, действительно большой. Не могли бы вы показать мне сам образ?   -  person Kujey    schedule 30.03.2015
comment
Ну, это будет либо изображение круговой диаграммы, либо отдельный фрагмент круговой диаграммы. На самом деле это довольно просто, но, конечно, анимация этих фрагментов, особенно с выровненными по центру изображениями, не является полностью плавной. С моим методом я обнаружил, что он плавный до 3-х срезов, для 4-х все нормально, но на 5-ти и выше он просто чертовски тормозит.   -  person user1710004    schedule 30.03.2015


Ответы (1)


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

- (void)drawChartWithFrame:(CGRect)frame startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle fillColor:(UIColor *)fillColor strokeColor:(UIColor *)strokeColor
{

    //// Oval Drawing
    CGRect ovalRect = CGRectMake(CGRectGetMinX(frame),CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame));
    UIBezierPath* ovalPath = UIBezierPath.bezierPath;
    [ovalPath addArcWithCenter: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) radius: CGRectGetWidth(ovalRect) / 2 startAngle: -(startAngle - 25) * M_PI/180 endAngle: -(endAngle - 93) * M_PI/180 clockwise: YES];
    [ovalPath addLineToPoint: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
    [ovalPath closePath];ansform ovalTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect));
    ovalTransform = CGAffineTransformScale(ovalTransform, 1, CGRectGetHeight(ovalRect) / CGRectGetWidth(ovalRect));
    [ovalPath applyTransform: ovalTransform];

    [fillColor setFill];
    [ovalPath fill];
    [strokeColor setStroke];
    ovalPath.lineWidth = 1;
    [ovalPath stroke];
}

Гораздо легче анимировать реальное изображение.

person Kujey    schedule 30.03.2015
comment
Я умею рисовать круговую диаграмму. Я пытаюсь динамически разместить изображение (чего угодно [дома, двери, человека, яблока, чего угодно]) в середине каждого отдельного фрагмента диаграммы. Затем я хочу анимировать это, и по логике изображение должно занимать столько же места, сколько и срез, а не перекрываться. - person user1710004; 31.03.2015