在动画期间无法正确定位子图层

Posted

技术标签:

【中文标题】在动画期间无法正确定位子图层【英文标题】:Trouble positioning a sublayer correctly during animation 【发布时间】:2020-07-04 02:23:52 【问题描述】:

问题是我应该如何定义和设置我的形状图层的位置以及应该如何更新它以便图层出现在动画期间我期望它出现的位置?即形状要粘在棍子的末端。

我有一个名为 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()

我也尝试过使用这样的表示层来设置位置,它似乎也接近了,但它仍然关闭。

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
        //...
    
    // ...

【问题讨论】:

在我看来,当调整大小开始时,您正在将圆硬设置为我们调整大小结束时的大小中心。 — 是的,您正在查看一层的self.bounds。但是当动画开始时,图层的边界会立即更改为动画结束时的边界。同时,在屏幕上绘制的不是图层,而是它的 presentation 图层。所以你没有与之协调。 是的,你会注意到你得到的both初始大小初始位置都是错误的。这是因为您从容器层在动画的 end 处将具有的边界中获取所有信息,而不是从它在开始时具有的尺寸到将具有的尺寸进行动画处理。结束。 为什么action(forKey key: #keyPath(position)) 不阻止ShapeLayer 的位置被动画化?看起来它应该是那么简单,我真的很困惑,它不能以这种方式工作。 对不起,我不明白我所说的与位置是否动画的概念有什么关系。我说的是代码中短语的含义,例如self.bounds,您可以使用它来定位和调整圆形的大小。我告诉你这些短语是什么意思。这是关于位置是什么,而不是你是否在到达那里的路上制作动画。 从这个角度来看,在我看来,你的标题,就像你的焦点一样,是一条红鲱鱼。这与禁用动画无关。这与self 是什么以及动画本身的工作原理有关。 【参考方案1】:

稍微夸张地说,我能够更清楚地说明您的代码中发生了什么。在我的示例中,圆形图层应该保持为背景视图高度的 1/3:

在动画开始时,背景视图已经在动画的结束设置为其最终大小。你看不到,因为动画依赖于描绘层的表示层,它是不变的;但观点本身已经改变。因此,当您定位形状层的形状并根据视图执行此操作时,您将调整大小并将其定位在动画结束时 需要的位置。因此它跳转到它的最终大小和位置,这只有在我们到达动画结束时才有意义。

好的,但现在考虑一下:

这不是更好吗?它是如何完成的?好吧,使用我已经described elsewhere 的原则,我有一个带有自定义动画属性的图层。结果是在动画的每一帧上,我都会收到一个事件(该层的draw(in:) 方法)。我通过重新计算形状层的路径来对此做出回应。因此,我在动画的每一帧上为形状层提供了一个新路径,因此它的行为很流畅。它停留在正确的位置,根据背景视图的大小平滑地调整大小,并且其笔触粗细始终保持不变。

【讨论】:

感谢您的其他示例,我现在可以看到我一直在寻找的那些中间值。有没有办法从draw(in:) 中捕获您通过“打印”看到的那些值并在绘图之外使用它们?我想用这些价值观做在draw(in:)内部完全不适合做的事情 例如,在我的 containerLayer 中,使用draw(in:) 中的打印,我正在观察其边界值在动画期间的变化。如何使用这些值作为同时设置 containerLayer 子层位置的基础? 在draw中给shape.position赋值可以吗? 你可以做任何你想做的事。正如我在书中所说:“抽象地考虑层动画作为让运行时计算并向我们发送一系列定时插值的一种方式。这些值通过运行时反复调用我们的draw(in:)实现,但没有法律规定我们必须在该实现中绘制:我们可以随意使用这些值。事实上,我们甚至不必使用这些值!我们可以实现draw(in:) 纯粹是为了在动画的每一帧上获取一个事件,以我们喜欢的任何方式响应该事件。” 那么这个形状是不是一个子层只是用一个椭圆填充它的边界,它的边界和位置由你从容器层中的draw(in:) 获得的键值修改?因为这就是我想要的。你用哪个键来告诉圆圈在哪里?

以上是关于在动画期间无法正确定位子图层的主要内容,如果未能解决你的问题,请参考以下文章

在动画 UIView 的中心定位子层

iOS核心动画之视觉效果

hashmapput和get值的时候是怎么确定key在数组中的位子的

hashmapput和get值的时候是怎么确定key在数组中的位子的

如何在更改其边界的动画期间强制重绘 CALayer

如何使用 CAShapeLayer 使我的 UIBezierPath 动画?