将CAShapeLayer圆形动画转换为圆角三角形

7
我想动画一个形状,使其从圆形过渡到圆角三角形。
TL;DR:如何在两个CGPath形状之间动画CAShapeLayer的路径? 我知道它们需要具有相同数量的控制点,但我认为我正在做到这一点 - 这段代码有什么问题?
开始和结束状态应该看起来像这样:transition http://clrk.it/4vJS+ 到目前为止,我尝试了以下方法:我正在使用CAShapeLayer,并动画更改其路径属性。
根据文档(我强调):documentation 路径对象可使用任何 CAPropertyAnimation 的具体子类进行动画处理。路径将作为“在线”点的线性混合插值;“离线”点可能会被非线性插值(例如,为了保持曲线导数的连续性)。如果两条路径具有不同数量的控制点或线段,则结果是未定义的。 为了让圆形和三角形具有相同数量的控制点,我将它们都制作成了四个点的形状:圆形是一个高度圆角矩形,而三角形在一侧“隐藏”了第四个控制点。
以下是我的代码:
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)
}

我的当前结果看起来像这样:

triangle-to-square

注意:我正在尝试使用非常圆润的正方形构建圆形,以尝试在矢量形状中获得相同数量的控制点。在上面的GIF中,已经减小了角半径以使变换更加明显。

你认为正在发生什么?还有什么其他方法可以实现这种效果?


尝试使用许多直线段来制作这两个形状? - nielsbot
我认为addArcToPoint()函数中的path参数不是可选的。 - nielsbot
你是想从一个正方形开始吗?你的问题是为什么你的起始形状看起来不像一个圆形? - nielsbot
@nielsbot - 啊,抱歉,我能理解那不是很清楚 - 我正在尝试从一个非常圆润的正方形构建一个圆来拥有相同数量的“控制点” - 我缩小了角半径以帮助我调试问题。 - bryanjclark
看起来你的角在动画过程中实际上是逆时针移动的...(仔细观察左侧的角)。有可能你的路径在前后并不具有相同数量的点吗?你可以迭代路径以打印出它包含的内容... - nielsbot
显示剩余2条评论
1个回答

6

由于addArcToPoint()函数会跳过创建一条线段的步骤,当路径的当前点和其第一个参数相等时,你最终得到的控制点数量并不相同。

根据文档(我加了重点):

如果弧的当前点和第一个切点(起始点)不相等,Quartz将从当前点到第一个切点附加一条直线段。

在创建三角形的代码中,这两个调用不会产生线段,而创建squircle的相应调用会产生:

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

你可以通过调用CGPathAddLineToPoint手动创建线条。你可以使用CGPathApplierFunction检查你的工作,它将让你枚举控制点。
此外~~~我可能建议旋转一个显示链接并编写一个函数,为每个t ∈ [0, 1]指定所需的形状,这可能是一个更清晰、更易于维护的解决方案。

非常感谢!是的,这将是一个交互式圆形 - 您将其向左拖动,圆形变成箭头,将其向右拖动,它就会朝另一个方向前进 - 所以我肯定需要编写该函数! - bryanjclark
1
Facebook的Pop框架是一种非常不错的方式,可以通过这些函数(请查看https://github.com/facebook/pop/blob/master/pop/POPAnimatableProperty.h)构建带有曲线、链接等动画。 - Andy Matuschak

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