【问题标题】:Scaling a CAShapeLayer with CABasicAnimation causes it to translate使用 CABasicAnimation 缩放 CAShapeLayer 会导致它平移
【发布时间】:2021-02-12 21:03:54
【问题描述】:

我有一个 CAShapeLayer,它被绘制为一个圆圈,作为给定的point

let circlePath = UIBezierPath(arcCenter: point, radius: 20.0, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 2.0

我希望能够使用 CABasicAnimation 缩放此图层。我尝试了几种方法,但大多数都给我同样的问题:圆圈适当缩放,但似乎同时平移/移动。圆圈缩放和平移的速率似乎会影响point 的值:point 离原点越近,圆圈的动画看起来越慢。我显然在这里遗漏了一些基本的东西,但我一直没能找到它。这是迄今为止我尝试过的最简单的构建动画的尝试:

let growthAnimation = CABasicAnimation(keyPath: "transform.scale")
growthAnimation.toValue = 3
growthAnimation.fillMode = .forwards
growthAnimation.isRemovedOnCompletion = false
growthAnimation.duration = 1.0
            
shapeLayer.add(growthAnimation, forKey: nil)
self.view.layer.addSublayer(shapeLayer)

我尝试设置“transform”(而不是“transform.scale”),我尝试使用 NSValue 作为动画的 toValue,我尝试将转换添加到转换以尝试保留它在当前位置,但没有运气。

我目前的理论是,图层实际上比圆圈更大,因此当整个图层缩放时,圆圈似乎只是平移。但我无法为图层的边框或背景颜色着色,而且我不确定如何证明这一点。

【问题讨论】:

  • 它的缩放对我来说很好,而且不动:screenshot
  • 有趣,所以问题似乎是 UIBezierPath 的 arcCenter 属性。如果你使用 CGPoint(100, 100) 而不是 CGPoint(0, 0),移动会发生在你身上吗?

标签: ios swift animation calayer


【解决方案1】:

“我目前的理论是,该层实际上比圆更大,因此随着整个层的缩放......”

你目前的理论是正确的。

试试这样:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    let radius: CGFloat = 20.0
    
    let point = CGPoint(x: radius, y: radius)
    let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = circlePath.cgPath
    shapeLayer.strokeColor = UIColor.black.cgColor
    shapeLayer.fillColor = nil
    shapeLayer.lineWidth = 2.0
    
    let growthAnimation = CABasicAnimation(keyPath: "transform.scale")
    growthAnimation.toValue = 3
    growthAnimation.fillMode = .forwards
    growthAnimation.isRemovedOnCompletion = false
    growthAnimation.duration = 1.0
    
    shapeLayer.add(growthAnimation, forKey: nil)

    // set the layer frame
    shapeLayer.frame = CGRect(x: view.frame.midX - radius, y: view.frame.midY - radius, width: radius * 2.0, height: radius * 2.0)
    
    shapeLayer.backgroundColor = UIColor.green.cgColor
    self.view.layer.addSublayer(shapeLayer)
    
}

编辑 - 这可能会让事情更清楚...

如果你运行这个例子,它会创建 4 个带有圆圈的形状层:

  • 圆心在0,0,没有给定图层框架
  • 圆心在100,100,没有给定图层框架
  • 圆心在0,0,图层框架x: 100, y: 240, w: 40, h: 40(40 是半径 * 2)...圆心是框架的左上角
  • 圆心在radius,radius(所以,20,20)与图层框架x: 100, y: 380, w: 40, h: 40(40 是半径 * 2)...圆心现在在图层框架中居中

当您点击视图上的任意位置时,所有 4 个图层都将执行相同的缩放动画(减慢到 3 秒以使其更易于观看)。

那么应该清楚路径和图层框架如何影响变换。

class ViewController: UIViewController {

    func test1() -> Void {

        let radius: CGFloat = 20.0

        let shapeLayer = CAShapeLayer()
        
        // no frame set for the shape layer

        // point is at top-left corner of shape layer frame
        //  since we haven't set a frame for the layer, it's top-left corner of the view
        let point = CGPoint(x: 0, y: 0)

        let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
        shapeLayer.path = circlePath.cgPath
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = nil
        shapeLayer.lineWidth = 2.0
        
        self.view.layer.addSublayer(shapeLayer)
        
        shapeLayer.backgroundColor = UIColor.green.cgColor
        
    }

    func test2() -> Void {
        
        let radius: CGFloat = 20.0
        
        let shapeLayer = CAShapeLayer()
        
        // no frame set for the shape layer
        
        // set point to 100,100
        let point = CGPoint(x: 100, y: 100)
        
        let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
        shapeLayer.path = circlePath.cgPath
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = nil
        shapeLayer.lineWidth = 2.0
        
        self.view.layer.addSublayer(shapeLayer)
        
        shapeLayer.backgroundColor = UIColor.green.cgColor
        
    }

    func test3() -> Void {
        
        let radius: CGFloat = 20.0
        
        let shapeLayer = CAShapeLayer()

        // set shape layer frame to 40x40 at 100,240
        shapeLayer.frame = CGRect(x: 100.0, y: 240.0, width: radius * 2.0, height: radius * 2.0)

        // point is at top-left corner of shape layer frame
        let point = CGPoint(x: 0, y: 0)
        
        let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
        shapeLayer.path = circlePath.cgPath
        shapeLayer.strokeColor = UIColor.orange.cgColor
        shapeLayer.fillColor = nil
        shapeLayer.lineWidth = 2.0
        
        self.view.layer.addSublayer(shapeLayer)
        
        shapeLayer.backgroundColor = UIColor.green.cgColor
        
    }
    
    func test4() -> Void {
        
        let radius: CGFloat = 20.0

        let shapeLayer = CAShapeLayer()

        // set shape layer frame to 40x40 at 100,380
        shapeLayer.frame = CGRect(x: 100.0, y: 380.0, width: radius * 2.0, height: radius * 2.0)

        // set point to center of layer frame
        let point = CGPoint(x: radius, y: radius)
        
        let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
        shapeLayer.path = circlePath.cgPath
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.fillColor = nil
        shapeLayer.lineWidth = 2.0
        
        self.view.layer.addSublayer(shapeLayer)
        
        shapeLayer.backgroundColor = UIColor.green.cgColor
        
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.setNavigationBarHidden(true, animated: false)
        let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap))
        view.addGestureRecognizer(t)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        test1()
        test2()
        test3()
        test4()
        
    }
    
    @objc func didTap() -> Void {
        scaleLayers()
    }
    
    func scaleLayers() -> Void {
        
        let growthAnimation = CABasicAnimation(keyPath: "transform.scale")
        growthAnimation.toValue = 3
        growthAnimation.fillMode = .forwards
        growthAnimation.isRemovedOnCompletion = false
        growthAnimation.duration = 3.0

        guard let layers = view.layer.sublayers else {
            return
        }
        layers.forEach { layer in
            if let shapeLayer = layer as? CAShapeLayer {
                shapeLayer.add(growthAnimation, forKey: nil)
            }
        }

    }
}

【讨论】:

  • 这似乎对我没有任何影响,圆圈还在移动。我很好奇您为什么使用半径值作为 CGPoint 的坐标?我注意到该点的 x 和 y 值越大,圆移动得越快
  • @WongWray - 您是否完全运行了我发布的代码?听起来不像。我用一个更详细的例子编辑了我的答案,应该为你澄清事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-19
相关资源
最近更新 更多