简体   繁体   English

将CAShapeLayer圆圈设置为圆角三角形

[英]Animate a CAShapeLayer circle into a rounded-corner triangle

I'd like to animate a shape as it transitions from a circle to a rounded-corner triangle. 我想为一个形状设置动画,因为它从圆形过渡到圆角三角形。

TL;DR: How do I animate a CAShapeLayer 's path between two CGPath shapes? TL; DR:如何在两个CGPath形状之间设置CAShapeLayer path动画? I know that they need to have the same number of control points, but I think I'm doing that - what's wrong with this code? 我知道他们需要拥有相同数量的控制点,但我认为我这样做 - 这段代码出了什么问题?

The beginning and end states would look something like this: transition http://clrk.it/4vJS+ 开始和结束状态看起来像这样: 转换http://clrk.it/4vJS+

Here's what I've tried so far: I'm using a CAShapeLayer , and animating a change in its path property. 以下是我到目前为止所尝试的内容:我正在使用CAShapeLayer ,并对其path属性进行动画更改。

According to the documentation (emphasis mine): 根据文件 (强调我的):

The path object may be animated using any of the concrete subclasses of CAPropertyAnimation . 可以使用CAPropertyAnimation任何具体子类对路径对象进行动画CAPropertyAnimation Paths will interpolate as a linear blend of the "on-line" points; 路径将插值为“在线”点的线性混合; "off-line" points may be interpolated non-linearly (eg to preserve continuity of the curve's derivative). “离线”点可以非线性插值(例如,以保持曲线导数的连续性)。 If the two paths have a different number of control points or segments the results are undefined. 如果两个路径具有不同数量的控制点或段,则结果未定义。

In an attempt to get the circle and triangle to have the same number of control points, I made them both four-pointed shapes: the circle is a heavily-rounded rectangle, and the triangle is "hiding" a fourth control point on one side. 为了使圆和三角形具有相同数量的控制点,我将它们制成四角形:圆形是一个重圆角矩形,三角形“隐藏”一侧的第四个控制点。

Here's my code: 这是我的代码:

self.view.backgroundColor = .blueColor()

//CREATE A CASHAPELAYER
let shape = CAShapeLayer()
shape.frame = CGRect(x: 50, y: 50, width: 200, height: 200)
shape.fillColor = UIColor.redColor().CGColor
self.view.layer.addSublayer(shape)

let bounds = shape.bounds

//CREATE THE SQUIRCLE
let squareRadius: CGFloat = CGRectGetWidth(shape.bounds)/2

let topCorner = CGPointMake(bounds.midX, bounds.minY)
let rightCorner = CGPointMake(bounds.maxX, bounds.midY)
let bottomCorner = CGPointMake(bounds.midX, bounds.maxY)
let leftCorner = CGPointMake(bounds.minX, bounds.midY)

let squarePath = CGPathCreateMutable()
let squareStartingPoint = midPoint(leftCorner, point2: topCorner)
CGPathMoveToPoint(squarePath, nil, squareStartingPoint.x, squareStartingPoint.y)
addArcToPoint(squarePath, aroundPoint: topCorner, onWayToPoint: rightCorner, radius: squareRadius)
addArcToPoint(squarePath, aroundPoint: rightCorner, onWayToPoint: bottomCorner, radius: squareRadius)
addArcToPoint(squarePath, aroundPoint: bottomCorner, onWayToPoint: leftCorner, radius: squareRadius)
addArcToPoint(squarePath, aroundPoint: leftCorner, onWayToPoint: topCorner, radius: squareRadius)
CGPathCloseSubpath(squarePath)
let square = UIBezierPath(CGPath: squarePath)

//CREATE THE (FAKED) TRIANGLE
let triangleRadius: CGFloat = 25.0

let trianglePath = CGPathCreateMutable()
let triangleStartingPoint = midPoint(topCorner, point2: rightCorner)

let startingPoint = midPoint(topCorner, point2: leftCorner)
CGPathMoveToPoint(trianglePath, nil, startingPoint.x, startingPoint.y)
let cheatPoint = midPoint(topCorner, point2:bottomCorner)
addArcToPoint(trianglePath, aroundPoint: topCorner, onWayToPoint: cheatPoint, radius: triangleRadius)
addArcToPoint(trianglePath, aroundPoint: cheatPoint, onWayToPoint: bottomCorner, radius: triangleRadius)
addArcToPoint(trianglePath, aroundPoint: bottomCorner, onWayToPoint: leftCorner, radius: triangleRadius)
addArcToPoint(trianglePath, aroundPoint: leftCorner, onWayToPoint: topCorner, radius: triangleRadius)
CGPathCloseSubpath(trianglePath)
let triangle = UIBezierPath(CGPath: trianglePath)


shape.path = square.CGPath

...and later on, in viewDidAppear : ......以及稍后,在viewDidAppear

let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = self.square.CGPath
animation.toValue = self.triangle.CGPath
animation.duration = 3
self.shape.path = self.triangle.CGPath
self.shape.addAnimation(animation, forKey: "animationKey")

I have two quick little functions for making the code more legible: 我有两个快速的小函数,使代码更清晰:

func addArcToPoint(path: CGMutablePath!, aroundPoint: CGPoint, onWayToPoint: CGPoint, radius: CGFloat) {
    CGPathAddArcToPoint(path, nil, aroundPoint.x, aroundPoint.y, onWayToPoint.x, onWayToPoint.y, radius)
}

func midPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
    return CGPointMake((point1.x + point2.x)/2, (point1.y + point2.y)/2)
}

My current result looks like this: 我目前的结果如下:

三角形到方

NOTE: I'm trying to build the circle out of a very-rounded square, in an attempt to get the same number of control points in the vector shape. 注意:我试图从一个非常圆的方块构建圆,试图在矢量形状中获得相同数量的控制点。 In the above GIF, the corner radius has been reduced to make the transformation more visible. 在上面的GIF中,角半径已经减小,使转换更加明显。

What do you think is going on? 你觉得怎么样? How else might I achieve this effect? 我怎么能达到这个效果呢?

You're not ending up with the same number of control points because addArcToPoint() skips creating a line segment if the path's current point and its first argument are equal. 你不会得到相同数量的控制点,因为如果路径的当前点和它的第一个参数相等, addArcToPoint()跳过创建一个线段。

From its documentation (emphasis mine): 从其文档(强调我的):

If the current point and the first tangent point of the arc (the starting point) are not equal , Quartz appends a straight line segment from the current point to the first tangent point. 如果当前点和弧的第一个切点(起点) 不相等 ,则Quartz会将当前点的直线段附加到第一个切点。

These two calls in your triangle-making code won't produce line segments, while the corresponding calls making the squircle will: 你的三角形代码中的这两个调用不会产生线段,而制作squircle的相应调用将:

addArcToPoint(trianglePath, aroundPoint: cheatPoint, onWayToPoint: bottomCorner, radius: triangleRadius)
addArcToPoint(trianglePath, aroundPoint: bottomCorner, onWayToPoint: leftCorner, radius: triangleRadius)

You can manually create the lines, though, by calling CGPathAddLineToPoint . 但是,您可以通过调用CGPathAddLineToPoint手动创建行。 And you can check your work using CGPathApplierFunction , which will let you enumerate the control points. 您可以使用CGPathApplierFunction检查您的工作,它可以让您枚举控制点。


Also~~~ I might suggest that spinning a display link and writing a function which specifies the desired shape for every t ∈ [0, 1] is probably going to be a clearer, more maintainable solution. 还~~~我可能会建议旋转显示链接并编写一个函数来指定每个t ∈ [0, 1]所需的形状,这可能是一个更清晰,更易维护的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM