CAShapeLayer渲染问题

时间:2016-01-22 09:58:51

标签: ios objective-c cashapelayer

我正在尝试通过添加具有不同笔触颜色的CAShapeLayer来创建圆形动画:

    UIView *view = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    [self.view addSubview:view];
    view.backgroundColor = [UIColor redColor];
    [self addCircleAnimationTo:view direction:YES];

- (void)addCircleAnimationTo:(UIView *)view direction:(BOOL)direction {
    CGFloat animationTime = 3;

    // Set up the shape of the circle
    int radius = view.frame.size.width/2;
    CAShapeLayer *circle = [CAShapeLayer layer];

    // Make a circular shape
    circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
                                             cornerRadius:radius].CGPath;

    // Center the shape in view
    circle.position = CGPointMake(radius,
                                  radius);

    circle.bounds = view.bounds;
    // Configure the apperence of the circle
    circle.fillColor = [UIColor clearColor].CGColor;

    // switch color
    circle.strokeColor = (direction) ? [UIColor blueColor].CGColor : [UIColor whiteColor].CGColor;
    circle.lineWidth = 5;
    circle.lineJoin = kCALineJoinBevel;
    circle.strokeStart = .0;
    circle.strokeEnd = 1.;

    // Configure animation
    CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    drawAnimation.duration            = animationTime;
    drawAnimation.repeatCount         = 1;

    // Animate from no part of the stroke being drawn to the entire stroke being drawn
    drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    drawAnimation.toValue   = [NSNumber numberWithFloat:1.];

    // Experiment with timing to get the appearence to look the way you want
    drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

    // Add to parent layer
    [view.layer addSublayer:circle];
    // Add the animation to the circle
    [circle addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((animationTime-0.1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self addCircleAnimationTo:view direction:!direction];
    });
}

enter image description here

我想知道为什么在白色图层动画时我能看到蓝色以及如何摆脱这种奇怪的蓝色边框:

enter image description here

2 个答案:

答案 0 :(得分:2)

您知道在每次调用addCircleAnimationTo后都要添加越来越多的形状图层吗?

在为新图层设置动画时,旧形状图层会在新图层下方提醒。这就是你看到它的原因。

[编辑2]简短说明

我们首先使用蓝色路径颜色创建两个图层,然后使用白色路径颜色创建第二个图层。

对于第一层,我们添加填充动画 - 更改layer.strokeEnd属性,我们将其设置为0到1和1到1的动画。持续时间的一半从0到1和一半持续时间从1到1(视觉上没有任何反应,但需要)。

我们添加清晰动画 - 更改layer.strokeStart,我们将其设置为0到1的动画。此动画的持续时间是填充动画的持续时间的一半。开始时间也是填充动画的一半,因为我们希望在结束笔划仍然时移动笔划的开头。

我们将相同的动画添加到第二个白色图层,但具有适当的开始时间偏移,这要归功于白色路径展开和蓝色路径风。

[编辑]添加实际答案

以下是我解决动画问题的建议。希望它有所帮助...... :)

- (void)viewDidLoad {
    [super viewDidLoad];

    UIView *view = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    [self.view addSubview:view];
    view.backgroundColor = [UIColor redColor];

    self.circle1 = [self circle:view.bounds];
    self.circle1.strokeColor = [UIColor blueColor].CGColor;
    self.circle2 = [self circle:view.bounds];
    self.circle2.strokeColor = [UIColor whiteColor].CGColor;

    [view.layer addSublayer:self.circle1];
    [view.layer addSublayer:self.circle2];

    [self.circle1 addAnimation:[self fillClearAnimation:0] forKey:@"fillclear"];
    [self.circle2 addAnimation:[self fillClearAnimation:3] forKey:@"fillclear"];
}

- (CAAnimationGroup *)fillClearAnimation:(CFTimeInterval)offset
{
    CGFloat animationTime = 3;

    CAAnimationGroup *group = [CAAnimationGroup animation];

    CAKeyframeAnimation *fill = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
    fill.values = @[ @0,@1,@1];
    fill.duration = 2*animationTime;
    fill.beginTime = 0.f;
    CAKeyframeAnimation *clear = [CAKeyframeAnimation animationWithKeyPath:@"strokeStart"];
    clear.values = @[ @0, @1];
    clear.beginTime = animationTime;
    clear.duration = animationTime;

    group.animations = @[ fill, clear ];
    group.duration = 2*animationTime;
    group.beginTime = CACurrentMediaTime() + offset;

    group.repeatCount = FLT_MAX;

    return group;
}

- (CAShapeLayer *)circle:(CGRect)frame
{
    CAShapeLayer *circle = [CAShapeLayer layer];
    CGFloat radius = ceil(frame.size.width/2);

    // Make a circular shape
    circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
                                             cornerRadius:radius].CGPath;

    // Center the shape in view
    circle.position = CGPointMake(radius,
                                  radius);

    circle.frame = frame;
    // Configure the apperence of the circle
    circle.fillColor = [UIColor clearColor].CGColor;

    circle.lineWidth = 5;
    circle.lineJoin = kCALineJoinBevel;
    circle.strokeStart = 0.f;
    circle.strokeEnd = 0.f;

    return circle;
}

答案 1 :(得分:1)

抗锯齿是造成问题的原因

当白色CAShapeLayer被绘制为蓝色时,边缘会与创建抗锯齿的前一个边缘混合。

唯一真正的解决方案是在制作动画时从白色下方清除蓝色。您可以通过从strokeStart动画显示上一个图层的0.0属性来执行此操作。到1.0。只需更改drawAnimation,您就可以重新使用keyPath。例如,将您的dispatch_after更改为:

- (void)addCircleAnimationTo:(UIView *)view direction:(BOOL)direction {

    // Insert your previous animation code, where drawAnimation is defined and added to the circle layer.    


    drawAnimation.keyPath = @"strokeStart"; // Re-use the draw animation object, setting it to animate strokeStart now.

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((animationTime-0.1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self addCircleAnimationTo:view direction:!direction];
        [circle addAnimation:drawAnimation forKey:@"(un)drawCircleAnimation"]; // Sets the previous layer to animate out
    });

}

这样,抗锯齿边缘混合从蓝色层中清除,使白色层得到一个漂亮的&清洁抗锯齿。 一旦您将图层设置为动画,就不要忘记删除该图层!

另一个可能的解决方案是增加白色图层的笔触宽度,因此它会在蓝色图层的边缘混合上进行描边,但我认为它会产生不希望的效果。你也可以禁用抗锯齿功能,但这在圆圈上看起来很糟糕。