iPhone核心动画 - 绘制圆形

51

我想要创建一段动画。最好的解释方法是向您展示画一个圆圈的过程。它从顶部或12点钟方向开始,按顺时针方向绘制直到完成整个圆,在10秒左右的时间内完成。

我已经尝试使用核心动画(Core Animation)绘制一个围绕中心点旋转的点 (使用此处提供的示例代码here)。但是我不知道该如何绘制圆圈?非常欢迎任何建议。

感谢您 :)


你是想要绘制一个圆形扇形,使角度逐渐增加直到成为完整的圆形(填充),还是只是绘制一条曲线,直到成为完整的圆形(只有轮廓,不填充)? - David Rönnqvist
@DavidRönnqvist 我正在尝试绘制一条曲线,直到它成为一个完整的圆形,只是轮廓而不填充 :) - bennythemink
4个回答

162
你真正应该做的是动画化CAShapeLayer的描边,其中路径是一个圆。这将使用核心动画进行加速,并且比在-drawRect:中绘制部分圆形要简洁得多。
下面的代码将在屏幕中心创建一个圆形图层,并沿顺时针方向动画显示其描边,使其看起来像正在被绘制。当然,您可以使用任何形状。(您可以阅读Ole Begemann的博客上的这篇文章了解有关如何动画化形状层描边的更多信息。) 注意:描边属性与图层上的边框属性不同。要更改描边的宽度,应使用"lineWidth"而不是"borderWitdh"等。
// Set up the shape of the circle
int radius = 100;
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 self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius, 
                              CGRectGetMidY(self.view.frame)-radius);

// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blackColor].CGColor;
circle.lineWidth = 5;

// Add to parent layer
[self.view.layer addSublayer:circle];

// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
drawAnimation.duration            = 10.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount         = 1.0;  // Animate only once..

// 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.0f];

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

// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

1
谢谢David提供这么棒的代码!我有一个问题:如何使整个圆形填充动画,而不仅仅是曲线?(这是你在评论bennythemink的问题时问的第一件事) - uerceg
3
还有其他的方法。你可以创建一个自定义图层并对其进行动画处理。可以参考这篇关于如何使用自定义CALayer来对饼状图进行动画处理的优秀教程:http://blog.pixelingene.com/2012/02/animating-pie-slices-using-a-custom-calayer/ - David Rönnqvist
1
你需要在一个单独的图层中绘制完整的渐变,并使用动画圆形作为遮罩。当你动画填充圆形遮罩时,渐变将变得可见。 - David Rönnqvist
1
@CyrilMestrom 是的,所有的动画都是这样的。一旦动画完成,它们会回到“真实”的值。你也必须在图层上设置 strokeEnd 属性为 0.5。这个例子没有做到,因为 1.0 是默认值。 - David Rönnqvist
1
我改变了我的代码,使用bezierPathWithArcCenter代替bezierPathWithRoundedRect,因为我更喜欢它给我的坐标系,而且可以反向旋转动画...但这节省了我很多时间,非常感谢! :) - jowie
显示剩余7条评论

8

这里是另一种解决问题的方法,基于@David的答案。这种方法允许您设置圆形动画的方向,并提供稍微更多的控制。编辑:我已经在如何使用Swift绘制圆形上写了一篇博客文章,我会尽量跟上最新版本。如果下面的代码对您不起作用,请在那里检查。

let radius = 100.0

// Create the circle layer
var circle = CAShapeLayer()

// Set the center of the circle to be the center of the view
let center = CGPointMake(CGRectGetMidX(self.frame) - radius, CGRectGetMidY(self.frame) - radius)

let fractionOfCircle = 3.0 / 4.0

let twoPi = 2.0 * Double(M_PI)
// The starting angle is given by the fraction of the circle that the point is at, divided by 2 * Pi and less
// We subtract M_PI_2 to rotate the circle 90 degrees to make it more intuitive (i.e. like a clock face with zero at the top, 1/4 at RHS, 1/2 at bottom, etc.)
let startAngle = Double(fractionOfCircle) / Double(twoPi) - Double(M_PI_2)
let endAngle = 0.0 - Double(M_PI_2)
let clockwise: Bool = true

// `clockwise` tells the circle whether to animate in a clockwise or anti clockwise direction
circle.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).CGPath

// Configure the circle
circle.fillColor = UIColor.blackColor().CGColor
circle.strokeColor = UIColor.redColor().CGColor
circle.lineWidth = 5

// When it gets to the end of its animation, leave it at 0% stroke filled
circle.strokeEnd = 0.0

// Add the circle to the parent layer
self.layer.addSublayer(circle)

// Configure the animation
var drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.repeatCount = 1.0

// Animate from the full stroke being drawn to none of the stroke being drawn
drawAnimation.fromValue = NSNumber(double: fractionOfCircle)
drawAnimation.toValue = NSNumber(float: 0.0)

drawAnimation.duration = 30.0

drawAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

// Add the animation to the circle
circle.addAnimation(drawAnimation, forKey: "drawCircleAnimation")

3
我想到了解决这个问题的方法,下面是代码。我似乎在解释手头的问题时搞错了,所以重写了另一个问题,并得到了正确的解决方案,详情请见此处。感谢大家的建议!
-(void)drawRect:(CGRect)rect
{
    // Drawing code

    CGRect allRect = self.bounds;
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Draw background
    CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
    CGContextSetLineWidth(context, 5);

    // Draw progress
    CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
    CGFloat radius = (allRect.size.width - 4) / 2;
    CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
    CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
    CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
    CGContextStrokePath(context);
}

0

好的,我有一个部分解决方案。我找到了以下绘制圆形线段的代码。它基本上是一个UIView子类,每次需要更新其进度时都会触发其drawRect方法。现在我唯一的问题是如何更改它,以便不是绘制部分或切片,而是绘制圆形描边?

-(void)drawRect:(CGRect)rect 
{
    // Drawing code

    CGRect allRect = self.bounds;
    CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f);

    CGContextRef context = UIGraphicsGetCurrentContext();

    // Draw background
    CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
    CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 0.1f); // translucent white
    CGContextSetLineWidth(context, self.lineWidth);
    CGContextFillEllipseInRect(context, circleRect);
    CGContextStrokeEllipseInRect(context, circleRect);

    // Draw progress
    CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
    CGFloat radius = (allRect.size.width - 4) / 2;
    CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
    CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
    CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white
    CGContextMoveToPoint(context, center.x, center.y);
    CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
    CGContextClosePath(context);
    CGContextFillPath(context);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接