动画贝塞尔曲线就像在 iPhone 屏幕上进行绘图一样

Animating Bezier curve as though drawing proceeds on iPhone screen

我想画一条泰姬陵陵墓轮廓的贝塞尔曲线。

Q1。我怎样才能在 XCode 中绘制它?我可以在 PS 中轻松做到这一点,但我发现在 XCode 中很难以编程方式做到这一点,尤其是获取控制点的坐标。

Q2。还有,屏幕尺寸变化时,如何固定点的坐标?

Q3。我如何制作动画,就好像一支看不见的铅笔在屏幕上画画一样?

我在 2010 年写过关于此的博客:Animating the Drawing of a CGPath With CAShapeLayer

它的要点是这样的:

  • 创建一个 UIBezierPathCGPath 用你想画的形状。使用moveToPoint:addLineToPoint: addCurveToPoint:controlPoint1:controlPoint2:等方法创建形状。
  • 创建一个CGShapeLayer并将路径分配给图层的path 属性.
  • 将图层添加到您的 view/layer 层次结构中(例如在视图控制器中:[self.view.layer addSublayer:shapeLayer];)。您还必须为图层提供有效的大小和位置 (frame)。
  • 动画层的 strokeEnd 属性 从 0 到 1。

如何在 UIKit 中绘制贝塞尔曲线路径?

创建一个 UIBezierPath 对象,然后使用 various methods on it to construct your path

我想您最感兴趣的方法是:

例如,像这样的东西将创建一个带有二次曲线的简单贝塞尔曲线路径:

UIBezierPath* yourPath = [UIBezierPath bezierPath]; // create the bezier path
[yourPath moveToPoint:CGPointMake(100, 100)]; // move to your starting point
[yourPath addQuadCurveToPoint:CGPointMake(300, 500) controlPoint:CGPointMake(500, 350)]; // add a new quad curve to the point

但是,如果您习惯于使用 Photoshop 之类的工具来创建路径,那么您 may be interested in PaintCode 可以让您使用 'what you see is what you get' 方法创建贝塞尔曲线路径。

您可以将此 UIBezierPath 添加到 CAShapeLayerpath 属性 以便在屏幕上显示它。

CAShapeLayer* yourShapeLayer = [CAShapeLayer layer]; // create your shape layer
yourShapeLayer.frame = CGRectMake(0, 0, 300, 500); // assign it's frame
yourShapeLayer.path = yourPath.CGPath; // add your path to the shape layer
yourShapeLayer.fillColor = [UIColor clearColor].CGColor; // prevent the shape layer from filling

[self.view.layer addSublayer:yourShapeLayer]; // add the shape layer to the view

然后您可以轻松地在形状图层上设置 strokeColorlineWidth 属性,以自定义路径的描边方式。

yourShapeLayer.strokeColor = [UIColor redColor].CGColor; // red stroke color
yourShapeLayer.lineWidth = 5.0; // 5 point stroke width

你应该得到这样的结果:


我如何制作动画,就像一支看不见的铅笔在屏幕上画画一样?

您可以轻松创建一个 CABasicAnimation,它可以为 CAShapeLayerstrokeStartstrokeEnd 属性 设置动画,以实现您想要的动画效果。

这会导致 'getting drawn' 行的动画效果出现在屏幕上。

例如,此代码将导致 strokeEnd 属性 从 0.0 动画到 1.0,创建您想要的效果:

yourShapeLayer.strokeStart = 0.0; // reset stroke start before animating

CABasicAnimation* strokeAnim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; // create animation that changes the strokeEnd property
strokeAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; // give it a nice timing function
strokeAnim.duration = 2.0; // duration of the animation
strokeAnim.fromValue = @(0.0); // provide the start value for the animation, wrapped in an NSNumber literal.
strokeAnim.toValue = @(1.0); // provide the end value for the animation, wrapped in an NSNumber literal.

[yourShapeLayer addAnimation:strokeAnim forKey:@"strokeAnim"]; // add animation to layer


如何缩放此路径以适应不同尺寸的屏幕?

我通常喜欢解决这个问题的方法是在给定的固定框架中定义路径的点(比如500 x 500点),然后根据屏幕大小生成一个比例因子(在你的例子中,你想缩放到屏幕的宽度)。

所以我们要做的就是生成比例因子。

CGFloat sf = self.view.frame.size.width/500.0; // The factor to scale the path by

然后,我们只需要将 UIBezierPath 中的所有点乘以这个比例因子即可。比如走我们之前的路径:

UIBezierPath* yourPath = [UIBezierPath bezierPath]; // create the bezier path
[yourPath moveToPoint:CGPointMake(100.0*sf, 100.0*sf)]; // move to your starting point
[yourPath addQuadCurveToPoint:CGPointMake(300.0*sf, 500.0*sf) controlPoint:CGPointMake(500.0*sf, 350.0*sf)];

现在,我们所要做的就是在我们的贝塞尔路径点中编程就好像我们总是在 500 x 500 的框架中绘制它(我的观点已经很适合了)。

您还需要更改 CAShapeLayer 的大小,使其呈方形并占据整个屏幕宽度。

yourShapeLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.width);

最后,您要将 CAShapeLayer 定位到屏幕中央。您可以通过将图层的 position 属性 设置为视图的 center 来轻松完成此操作。

yourShapeLayer.position = self.view.center;

现在您的路径应该始终缩放以占据屏幕的宽度。

(我在图层中添加了灰色背景只是为了说明缩放比例)


完整项目(为方便起见):https://github.com/hamishknight/Drawing-Scaling-Animating-UIBezierPaths

originaluser2发布的答案解决了一切。如果你想为不同的设备屏幕调整贝塞尔路径的大小,你也可以尝试这种方式:

您可以简单地将 UIView 子类化并自定义其 drawRect 方法:

  - (void)drawRect: (CGRect)frame
{

    //// Bezier Drawing
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.23370 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.65441 * CGRectGetHeight(frame))];
[bezierPath addCurveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.49457 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.33088 * CGRectGetHeight(frame)) controlPoint1: CGPointMake(CGRectGetMinX(frame) + 0.23370 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.65441 * CGRectGetHeight(frame)) controlPoint2: CGPointMake(CGRectGetMinX(frame) + 0.35870 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.33088 * CGRectGetHeight(frame))];
[bezierPath addCurveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.77717 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.65441 * CGRectGetHeight(frame)) controlPoint1: CGPointMake(CGRectGetMinX(frame) + 0.63043 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.33088 * CGRectGetHeight(frame)) controlPoint2: CGPointMake(CGRectGetMinX(frame) + 0.77717 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.65441 * CGRectGetHeight(frame))];
[UIColor.blackColor setStroke];
bezierPath.lineWidth = 1;
[bezierPath stroke];

}

帧 50x50:

帧 100x100:

帧 100x50

如果您发送大小为 50x50 的帧,这将在大小为 50x50 的帧中绘制贝塞尔曲线。此外,此代码会根据收到的帧大小自动调整大小。