彩色动画

Color animation

我已经编写了简单的动画来绘制线条中的矩形,我们可以将它们视为条形。

每个条都是一个形状层,它有一个动画路径(大小变化和填充颜色变化)。

@IBDesignable final class BarView: UIView {

    lazy var pathAnimation: CABasicAnimation = {
        let animation = CABasicAnimation(keyPath: "path")
        animation.duration = 1
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        animation.fillMode = kCAFillModeBoth
        animation.isRemovedOnCompletion = false
        return animation
    }()

    let red = UIColor(red: 249/255, green: 26/255, blue: 26/255, alpha: 1)
    let orange = UIColor(red: 1, green: 167/255, blue: 463/255, alpha: 1)
    let green = UIColor(red: 106/255, green: 239/255, blue: 47/255, alpha: 1)

    lazy var backgroundColorAnimation: CABasicAnimation = {
        let animation = CABasicAnimation(keyPath: "fillColor")
        animation.duration = 1
        animation.fromValue = red.cgColor
        animation.byValue = orange.cgColor
        animation.toValue = green.cgColor
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        animation.fillMode = kCAFillModeBoth
        animation.isRemovedOnCompletion = false
        return animation
    }()

    @IBInspectable var spaceBetweenBars: CGFloat = 10

    var numberOfBars: Int = 5
    let data: [CGFloat] = [5.5, 9.0, 9.5, 3.0, 8.0]

    override func awakeFromNib() {
        super.awakeFromNib()
        initSublayers()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        setupLayers()
    }

    func setupLayers() {
        let width = bounds.width - (spaceBetweenBars * CGFloat(numberOfBars + 1)) // There is n + 1 spaces between bars.
        let barWidth: CGFloat = width / CGFloat(numberOfBars)
        let scalePoint: CGFloat = bounds.height / 10.0 // 10.0 - 10 points is max

        guard let sublayers = layer.sublayers as? [CAShapeLayer] else { return }

        for i in 0...numberOfBars - 1 {
            let barHeight: CGFloat = scalePoint * data[i]
            let shapeLayer = CAShapeLayer()
            layer.addSublayer(shapeLayer)

            var xPos: CGFloat!
            if i == 0 {
                xPos = spaceBetweenBars
            } else if i == numberOfBars - 1 {
                xPos = bounds.width - (barWidth + spaceBetweenBars)
            } else {
                xPos = barWidth * CGFloat(i) + spaceBetweenBars * CGFloat(i) + spaceBetweenBars
            }

            let startPath = UIBezierPath(rect: CGRect(x: xPos, y: bounds.height, width: barWidth, height: 0)).cgPath
            let endPath = UIBezierPath(rect: CGRect(x: xPos, y: bounds.height, width: barWidth, height: -barHeight)).cgPath

            sublayers[i].path = startPath
            pathAnimation.toValue = endPath

            sublayers[i].removeAllAnimations()

            sublayers[i].add(pathAnimation, forKey: "path")
            sublayers[i].add(backgroundColorAnimation, forKey: "backgroundColor")
        }
    }


    func initSublayers() {
        for _ in 1...numberOfBars {
            let shapeLayer = CAShapeLayer()
            layer.addSublayer(shapeLayer)
        }
    }
}

条形的大小(高度)取决于data数组,每个子层都有不同的高度。基于这些数据,我制作了一个秤。

PathAnimation 正在改变条形的高度。 BackgroundColorAnimation 正在更改路径的颜色。它从红色开始,经过橙色,最后到绿色。

我的目标是将 backgroundColorAnimationdata 数组连接起来,同时它与 pathAnimation 连接起来。

例如。当 data 数组的值为 1.0 时,条形图将仅变为红色,这是从声明为全局变量的基本 red 颜色派生的。如果 data 数组中的值将是 ex。 4.5 然后颜色动画将停止接近 delcared 橙色,5.0 限制将是这个橙色或颜色接近这个。接近 10 的值将变为绿色。

如何将这些条件与动画属性联系起来 fromValuebyValuetoValue。这是一个算法吗?有任何想法吗 ?

你有几个问题。

  1. 您正在设置 fillModeisRemovedOnCompletion。这告诉我,直截了当地说,你不了解 Core Animation。你需要观看 WWDC 2011 Session 421: Core Animation Essentials.

  2. 每次调用 layoutSubviews 时都会添加更多层,但不会对它们进行任何操作。

  3. 您在每次 layoutSubviews 运行时添加动画。当双倍高度的“通话中”状态栏出现或消失时,或者在界面旋转时,你真的想重新设置动画吗?最好有一个单独的 animateBars() 方法,并从视图控制器的 viewDidAppear 方法中调用它。

  4. 你似乎认为byValue的意思是“在从fromValuetoValue的路上经过这个值”,但那不是这个意思。 byValue 在您的案例中被忽略,因为您正在设置 fromValuetoValuebyValue 的效果在 Setting Interpolation Values.

  5. 中解释
  6. 如果你想在颜色之间进行插值,最好使用基于色相的颜色space,但我相信Core Animation使用的是RGB颜色space。因此,您应该使用关键帧动画来指定通过在基于色调的颜色 space.

  7. 中进行插值来计算的中间颜色

这是对 BarView 的重写,修复了所有这些问题:

@IBDesignable final class BarView: UIView {

    @IBInspectable var spaceBetweenBars: CGFloat = 10

    var data: [CGFloat] = [5.5, 9.0, 9.5, 3.0, 8.0]
    var maxDatum = CGFloat(10)

    func animateBars() {
        guard window != nil else { return }

        let bounds = self.bounds
        var flatteningTransform = CGAffineTransform.identity.translatedBy(x: 0, y: bounds.size.height).scaledBy(x: 1, y: 0.001)
        let duration: CFTimeInterval = 1
        let frames = Int((duration * 60.0).rounded(.awayFromZero))

        for (datum, barLayer) in zip(data, barLayers) {
            let t = datum / maxDatum
            if let path = barLayer.path {
                let path0 = path.copy(using: &flatteningTransform)
                let pathAnimation = CABasicAnimation(keyPath: "path")
                pathAnimation.duration = 1
                pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
                pathAnimation.fromValue = path0
                barLayer.add(pathAnimation, forKey: pathAnimation.keyPath)

                let colors = gradient.colors(from: 0, to: t, count: frames).map({ [=10=].cgColor })
                let colorAnimation = CAKeyframeAnimation(keyPath: "fillColor")
                colorAnimation.timingFunction = pathAnimation.timingFunction
                colorAnimation.duration = duration
                colorAnimation.values = colors
                barLayer.add(colorAnimation, forKey: colorAnimation.keyPath)
            }
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        createOrDestroyBarLayers()

        let bounds = self.bounds
        let barSpacing = (bounds.size.width - spaceBetweenBars) / CGFloat(data.count)
        let barWidth = barSpacing - spaceBetweenBars

        for ((offset: i, element: datum), barLayer) in zip(data.enumerated(), barLayers) {
            let t = datum / maxDatum
            let barHeight = t * bounds.size.height
            barLayer.frame = bounds
            let rect = CGRect(x: spaceBetweenBars + CGFloat(i) * barSpacing, y: bounds.size.height, width: barWidth, height: -barHeight)
            barLayer.path = CGPath(rect: rect, transform: nil)
            barLayer.fillColor = gradient.color(at: t).cgColor
        }
    }

    private let gradient = Gradient(startColor: .red, endColor: .green)

    private var barLayers = [CAShapeLayer]()

    private func createOrDestroyBarLayers() {
        while barLayers.count < data.count {
            barLayers.append(CAShapeLayer())
            layer.addSublayer(barLayers.last!)
        }
        while barLayers.count > data.count {
            barLayers.removeLast().removeFromSuperlayer()
        }
    }

}

private extension UIColor {
    var hsba: [CGFloat] {
        var hue: CGFloat = 0
        var saturation: CGFloat = 0
        var brightness: CGFloat = 0
        var alpha: CGFloat = 0
        getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
        return [hue, saturation, brightness, alpha]
    }
}

private struct Gradient {
    init(startColor: UIColor, endColor: UIColor) {
        self.startColor = startColor
        self.startHsba = startColor.hsba
        self.endColor = endColor
        self.endHsba = endColor.hsba
    }

    let startColor: UIColor
    let endColor: UIColor

    let startHsba: [CGFloat]
    let endHsba: [CGFloat]

    func color(at t: CGFloat) -> UIColor {
        let out = zip(startHsba, endHsba).map { [=10=] * (1.0 - t) +  * t }
        return UIColor(hue: out[0], saturation: out[1], brightness: out[2], alpha: out[3])
    }

    func colors(from t0: CGFloat, to t1: CGFloat, count: Int) -> [UIColor] {
        var colors = [UIColor]()
        colors.reserveCapacity(count)
        for i in 0 ..< count {
            let s = CGFloat(i) / CGFloat(count - 1)
            let t = t0 * (1 - s) + t1 * s
            colors.append(color(at: t))
        }
        return colors
    }

}

结果: