首页 > 解决方案 > 如何为 CALayer 的角半径设置动画

问题描述

我正在尝试创建一个动画,其中 CALayer 矩形从与视图的边框齐平变为具有左、右或两个角的圆角,并且矩形的宽度也发生了变化。

我遇到的问题是动画看起来失真。如果我在动画中禁用角半径更改,则不会发生失真。我怀疑这是因为当动画中的原始路径和最终路径具有不同数量的圆角时,路径具有不同数量的控制点,因此路径动画行为根据文档未定义。

我在想我应该尝试找到一种方法,使圆角矩形中的控制点数等于非圆角矩形中的数字,但我不确定我将如何做到这一点,因为我没有' t 找到了一种计算 CGPath/UIBezierPath 中控制点数量的方法。

这是我现在正在使用的代码,我正在为路径设置动画,但我愿意通过使用两个矩形或类似的东西将实现完全更改为“作弊”。

func setSelection(to color: UIColor, connectedLeft: Bool, connectedRight: Bool, animated: Bool) {
    self.connectedLeft = connectedLeft
    self.connectedRight = connectedRight
    self.color = color
    if animated {
        let pathAnimation = CABasicAnimation(keyPath: "path")
        let colorAnimation = CABasicAnimation(keyPath: "fillColor")
        self.configure(animation: pathAnimation)
        self.configure(animation: colorAnimation)
        pathAnimation.toValue = self.rectPath(connectedLeft: connectedLeft, connectedRight: connectedRight).cgPath
        colorAnimation.toValue = color.cgColor
        let group = CAAnimationGroup()
        group.animations = [pathAnimation, colorAnimation]
        self.configure(animation: group)
        group.delegate = self
        self.rectLayer.add(group, forKey: Constants.selectionChangeAnimationKey)
    } else {
        self.rectLayer.fillColor = color.cgColor
        self.rectLayer.path = self.rectPath(connectedLeft: connectedLeft, connectedRight: connectedRight).cgPath
    }
}

private func rectPath(connectedLeft: Bool, connectedRight: Bool) -> UIBezierPath {
    let spacing: CGFloat = 5
    var origin = self.bounds.origin
    var size = self.bounds.size
    var corners = UIRectCorner()
    if !connectedLeft {
        origin.x += spacing
        size.width -= spacing
        corners.formUnion([.topLeft, .bottomLeft])
    }
    if !connectedRight {
        size.width -= spacing
        corners.formUnion([.topRight, .bottomRight])
    }
    let path = UIBezierPath(roundedRect: .init(origin: origin, size: size), byRoundingCorners: corners, cornerRadii: .init(width: 8, height: 8))
    print(path.cgPath)
    return path
}

标签: iosswiftcalayercaanimation

解决方案


您对动画无法正常工作的原因是正确的。

绘制弧线的核心图形/核心动画方法使用可变数量的三次贝塞尔曲线组成弧线,具体数量取决于绘制的角度。

我相信 90 度角被渲染为单个三次贝塞尔曲线。

根据我最近所做的一项测试,您似乎需要做的就是拥有与贝塞尔曲线相同数量的端点,以便正确绘制锐角到圆角动画。对于圆角,我很确定它会在圆角处绘制一条线段,然后将弧线绘制为一条贝塞尔曲线,然后是下一条线段。因此,您应该能够通过绘制每个尖角点两次来创建一个矩形。我没有尝试过,但我认为它会起作用。

(根据我对如何使用贝塞尔曲线绘制圆弧的理解,圆弧由绘制的每个四分之一圆或四分之一圆的一部分的三次贝塞尔曲线组成。所以对于 90 度弯曲,尖角只需要 2 个控制点来代替圆角。如果角度 > 90 度但小于 180,则需要在拐角处设置 3 个控制点,如果角度 > 180 但小于 270,则需要 4 个控制点。)

编辑:

上述为圆角矩形添加每个角点两次的方法应该适用于圆角矩形情况(每个角正好是 90 度),但它不适用于需要圆角变化的不规则多边形。我不知道如何处理这种情况。我已经创建了一个演示项目,该项目可以生成不规则的多边形,其中包含任何锐角和弯曲角的混合,因此我对其进行了调整以处理动画。

该项目现在包括一个函数,该函数生成CGPath带有可设置的圆角和尖角混合的 s,它将动画化尖角和圆角之间的过渡。你要查看的函数被调用roundedRectPath(rect:byRoundingCorners:cornerRadius:)

这是函数头和内联文档

/**
This function works like the UIBezierPath initializer `init(roundedRect:byRoundingCorners:cornerRadii:)` It returns a CGPath a rounded rectangle with a mixture of rounded and sharp corners, as specified by the options in `corners`.

 Unlike the UIBezierPath `init(roundedRect:byRoundingCorners:cornerRadii:` intitializer, The CGPath that is returned by this function will animate smoothly from rounded to non-rounded corners.

 - Parameter  rect: The starting rectangle who's corners you wish to round
 - Parameter corners: The corners to round
 - Parameter cornerRadius: The corner radius to use
 - Returns: A CGPath containing for the rounded rectangle.
*/
public func roundedRectPath(rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadius: CGFloat) -> CGPath

您可以从 Github 下载该项目并在此链接上试用。

这是运行中的演示应用程序的 GIF:

在此处输入图像描述


推荐阅读