彩色动画

时间:2018-01-16 17:12:47

标签: swift core-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正在改变路径的collors。它从红色开始,穿过橙色并以绿色结束。

我的目标是将backgroundColorAnimationdata数组相关联,并将其与pathAnimation相关联。

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

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

1 个答案:

答案 0 :(得分:1)

你有几个问题。

  1. 您正在设置fillModeisRemovedOnCompletion。这告诉我,直言不讳,你不了解核心动画。您需要观看WWDC 2011 Session 421: Core Animation Essentials

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

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

  4. 您似乎认为byValue表示“在从fromValuetoValue的路上经历此值”,但这并不意味着什么。在您的情况下会忽略byValue,因为您正在设置fromValuetoValuebyValue中解释了BarView的效果。

  5. 如果要在颜色之间进行插值,最好使用基于色调的颜色空间,但我相信Core Animation使用RGB颜色空间。因此,您应该使用关键帧动画来指定通过在基于色调的色彩空间中进行插值来计算的中间色。

  6. 这里重写了@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({ $0.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 { $0 * (1.0 - t) + $1 * 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 } } ,解决了所有这些问题:

    #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
    
    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) 
    {
      #ifdef __IPHONE_8_0
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert
                                                                                             | UIUserNotificationTypeBadge
                                                                                             | UIUserNotificationTypeSound) categories:nil];
        [application registerUserNotificationSettings:settings];
      #endif
    } 
    else 
    {
        UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
        [application registerForRemoteNotificationTypes:myTypes];
    }
    
    if(SYSTEM_VERSION_EQUAL_TO(@"10.0") ||SYSTEM_VERSION_EQUAL_TO(@"11.0") )
    {
        UNUserNotificationCenter *notifiCenter = [UNUserNotificationCenter currentNotificationCenter];
        notifiCenter.delegate = self;
        [notifiCenter requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error){
            if( !error ){
                [[UIApplication sharedApplication] registerForRemoteNotifications];
            }
        }];
    }
    

    结果:

    Setting Interpolation Values