问题是我应该如何定义和设置我的形状层的位置以及应该如何更新它以便该层出现在我期望它出现在动画期间的位置?也就是说,形状应该卡在棍子的末端。
我有一个名为
containerLayer
的CALayer实例,它有一个子层,它是一个名为shape
的CAShapeLayer实例。 containerLayer
应该将 shape
放在特定位置 unitLoc
像这样:
class ContainerLayer: CALayer, CALayerDelegate {
// ...
override func layoutSublayers() {
super.layoutSublayers()
if !self.didSetup {
self.setup()
self.didSetup = true
}
updateFigure()
setNeedsDisplay()
}
func updateFigure() {
figureCenter = self.bounds.center
figureDiameter = min(self.bounds.width, self.bounds.height)
figureRadius = figureDiameter/2
shapeDiameter = round(figureDiameter / 5)
shapeRadius = shapeDiameter/2
locRadius = figureRadius - shapeRadius
angle = -halfPi
unitLoc = CGPoint(x: self.figureCenter.x + cos(angle) * locRadius, y: self.figureCenter.y + sin(angle) * locRadius)
shape.bounds = CGRect(x: 0, y: 0, width: shapeDiameter, height: shapeDiameter)
shape.position = unitLoc
shape.updatePath()
}
// ...
}
我很难找到正确的方法来指定这个位置之前应该是什么,以及在改变
containerLayer.bounds
的调整大小动画期间。我确实明白我遇到的问题是我没有以动画将按照我期望的方式显示它的方式设置位置。
我尝试过使用
CABasicAnimation(keyPath: "position")
为位置设置动画,它比我之前尝试的结果有所改善,但它仍然关闭。
@objc func resize(sender: Any) {
// MARK:- animate containerLayer bounds & shape position
// capture bounds value before changing
let oldBounds = self.containerLayer.bounds
// capture shape position value before changing
let oldPos = self.containerLayer.shape.position
// update the constraints to change the bounds
isLarge.toggle()
updateConstraints()
self.view.layoutIfNeeded()
let newBounds = self.containerLayer.bounds
let newPos = self.containerLayer.unitLoc
// set up the bounds animation and add it to containerLayer
let baContainerBounds = CABasicAnimation(keyPath: "bounds")
baContainerBounds.fromValue = oldBounds
baContainerBounds.toValue = newBounds
containerLayer.add(baContainerBounds, forKey: "bounds")
// set up the position animation and add it to shape layer
let baShapePosition = CABasicAnimation(keyPath: "position")
baShapePosition.fromValue = oldPos
baShapePosition.toValue = newPos
containerLayer.shape.add(baShapePosition, forKey: "position")
containerLayer.setNeedsLayout()
self.view.layoutIfNeeded()
}
我也试过像这样用presentation layer来设置位置,好像也可以关闭,但是还是关闭了
class ViewController: UIViewController {
//...
override func loadView() {
super.loadView()
displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.default)
//...
}
@objc func animationDidUpdate(displayLink: CADisplayLink) {
let newCenter = self.containerLayer.presentation()!.bounds.center
let new = CGPoint(x: newCenter.x + cos(containerLayer.angle) * containerLayer.locRadius, y: newCenter.y + sin(containerLayer.angle) * containerLayer.locRadius)
containerLayer.shape.position = new
}
//...
}
class ContainerLayer: CALayer, CALayerDelegate {
// ...
func updateFigure() {
//...
//shape.position = unitLoc
//...
}
// ...
}
稍微夸张一下,我能够更清楚地说明您的代码中发生了什么。在我的示例中,圆形层应该保持背景视图高度的 1/3:
在动画开始时,背景视图在动画end 时已经设置为最终大小。你看不到,因为动画依赖于描绘层的表示层,它是不变的;但观点本身已经改变。因此,当您定位形状图层的形状时,并且根据视图进行定位时,您正在调整大小并将其定位在动画结束时将需要的位置。因此它跳到它的最终大小和位置,只有当我们到达动画结束时才有意义。
好吧,但现在考虑一下:
这样不是更好吗?它是如何完成的?好吧,使用我已经在别处描述的原则,我得到了一个具有自定义动画属性的层。结果是在动画的每一帧上,我都会得到一个事件(该层的
draw(in:)
方法)。我通过重新计算形状层的路径来对此做出回应。因此,我在动画的每一帧上为形状层提供了一条新路径,因此它表现得很流畅。它停留在正确的位置,它根据背景视图的大小平滑地调整大小,并且它的笔画粗细始终保持不变。
要听懂这里所说的一切有点困难。
但在 iOS 图层中尝试“自动”为自己制作动画。关闭隐式动画“很容易”,比如:
class RepairedCALayer: CALayer {
override open class func defaultAction(forKey event: String) -> CAAction? {
if event == #keyPath(bounds) { return NSNull() }
if event == #keyPath(position) { return NSNull() }
if event == #keyPath(isHidden) { return NSNull() }
return super.defaultAction(forKey: event)
}
}
再一次,我实在是太多了,没有时间阅读这个问题(抱歉!),但我经常受苦于你所展示的内容,因为内置的自动动画与你想要制作的动画冲突。
如果是这种情况,你就完成了——“简单地”以一种或另一种方式阻止隐式动画。
“关闭”隐式动画需要一生的研究,但我在这里包含了一个简单的方法。我希望它有所帮助,即使这不是您的问题。