将 CAShapeLayer 圆动画化为圆角三角形
Animate a CAShapeLayer circle into a rounded-corner triangle
我想在形状从圆形过渡到圆角三角形时制作动画。
TL;DR:如何在两个 CGPath
形状之间设置 CAShapeLayer
的 path
的动画?我知道他们需要有相同数量的控制点,但我想我正在这样做 - 这段代码有什么问题?
开始和结束状态看起来像这样:
transition http://clrk.it/4vJS+
这是我迄今为止尝试过的方法:我正在使用 CAShapeLayer
,并在其 path
属性.
中设置动画变化
根据 documentation(强调我的):
The path object may be animated using any of the concrete subclasses of CAPropertyAnimation
. Paths will interpolate as a linear blend of the "on-line" points; "off-line" points may be interpolated non-linearly (e.g. 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.
为了让圆和三角形有相同数量的控制点,我把它们都做成了四角形:圆是一个大圆角的矩形,三角形是 "hiding"一侧的第四个控制点。
这是我的代码:
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
...之后,在 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")
我有两个快速的小函数可以使代码更清晰:
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)
}
我目前的结果是这样的:
注意: 我试图用一个非常圆的正方形构建圆,试图在矢量形状中获得相同数量的控制点。在上面的 GIF 中,转角半径已减小,使变换更明显。
你认为这是怎么回事?我还能如何实现这种效果?
您不会以相同数量的控制点结束,因为如果路径的当前点与其第一个参数相等,addArcToPoint()
会跳过创建线段。
来自其文档(强调我的):
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.
您的三角形代码中的这两个调用不会生成线段,而生成圆弧形的相应调用会:
addArcToPoint(trianglePath, aroundPoint: cheatPoint, onWayToPoint: bottomCorner, radius: triangleRadius)
addArcToPoint(trianglePath, aroundPoint: bottomCorner, onWayToPoint: leftCorner, radius: triangleRadius)
不过,您可以通过调用 CGPathAddLineToPoint
手动创建线条。您可以使用 CGPathApplierFunction
检查您的工作,这将让您枚举控制点。
另外~~~ 我可能建议旋转显示器 link 并编写一个函数来为每个 t ∈ [0, 1]
指定所需的形状成为一个更清晰、更可维护的解决方案。
我想在形状从圆形过渡到圆角三角形时制作动画。
TL;DR:如何在两个 CGPath
形状之间设置 CAShapeLayer
的 path
的动画?我知道他们需要有相同数量的控制点,但我想我正在这样做 - 这段代码有什么问题?
开始和结束状态看起来像这样: transition http://clrk.it/4vJS+
这是我迄今为止尝试过的方法:我正在使用 CAShapeLayer
,并在其 path
属性.
根据 documentation(强调我的):
The path object may be animated using any of the concrete subclasses of
CAPropertyAnimation
. Paths will interpolate as a linear blend of the "on-line" points; "off-line" points may be interpolated non-linearly (e.g. 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.
为了让圆和三角形有相同数量的控制点,我把它们都做成了四角形:圆是一个大圆角的矩形,三角形是 "hiding"一侧的第四个控制点。
这是我的代码:
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
...之后,在 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")
我有两个快速的小函数可以使代码更清晰:
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)
}
我目前的结果是这样的:
注意: 我试图用一个非常圆的正方形构建圆,试图在矢量形状中获得相同数量的控制点。在上面的 GIF 中,转角半径已减小,使变换更明显。
你认为这是怎么回事?我还能如何实现这种效果?
您不会以相同数量的控制点结束,因为如果路径的当前点与其第一个参数相等,addArcToPoint()
会跳过创建线段。
来自其文档(强调我的):
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.
您的三角形代码中的这两个调用不会生成线段,而生成圆弧形的相应调用会:
addArcToPoint(trianglePath, aroundPoint: cheatPoint, onWayToPoint: bottomCorner, radius: triangleRadius)
addArcToPoint(trianglePath, aroundPoint: bottomCorner, onWayToPoint: leftCorner, radius: triangleRadius)
不过,您可以通过调用 CGPathAddLineToPoint
手动创建线条。您可以使用 CGPathApplierFunction
检查您的工作,这将让您枚举控制点。
另外~~~ 我可能建议旋转显示器 link 并编写一个函数来为每个 t ∈ [0, 1]
指定所需的形状成为一个更清晰、更可维护的解决方案。