如何使自定义视图的背景颜色属性可动画?

时间:2017-09-23 15:42:27

标签: ios swift animation calayer

我想为自定义视图的背景颜色设置动画。

我的绘图代码非常简单。 draw(in:)方法被覆盖如下:

@IBDesignable
class SquareView: UIView {
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        let strokeWidth = self.width / 8
        let path = UIBezierPath()
        path.move(to: CGPoint(x: self.width - strokeWidth / 2, y: 0))
        path.addLine(to: CGPoint(x: self.width - strokeWidth / 2, y: self.height - strokeWidth / 2))
        path.addLine(to: CGPoint(x: 0, y: self.height - strokeWidth / 2))
        self.backgroundColor?.darker().setStroke()
        path.lineWidth = strokeWidth
        path.stroke()
    }
}

darker方法只返回调用它的颜色的较暗版本。

当我将背景设置为蓝色时,它会绘制如下内容:

enter image description here

我想为其背景颜色设置动画,使其逐渐变为红色。这将是最终结果:

enter image description here

我第一次尝试:

UIView.animate(withDuration: 1) { 
    self.square.backgroundColor = .red
}

它只是瞬间将颜色变为红色,没有动画。

然后我研究并发现我需要使用CALayer s。因此,我尝试分层绘制事物:

@IBDesignable
class SquareViewLayers: UIView {
    dynamic var squareColor: UIColor = .blue {
        didSet {
            setNeedsDisplay()
        }
    }

    func setupView() {
        layer.backgroundColor = squareColor.cgColor
        let sublayer = CAShapeLayer()
        let path = UIBezierPath()
        let strokeWidth = self.width / 8
        path.move(to: CGPoint(x: self.width - strokeWidth / 2, y: 0))
        path.addLine(to: CGPoint(x: self.width - strokeWidth / 2, y: self.height - strokeWidth / 2))
        path.addLine(to: CGPoint(x: 0, y: self.height - strokeWidth / 2))
        self.tintColor.darker().setStroke()
        path.lineWidth = strokeWidth
        sublayer.path = path.cgPath
        sublayer.strokeColor = squareColor.darker().cgColor
        sublayer.lineWidth = strokeWidth
        sublayer.backgroundColor = UIColor.clear.cgColor
        layer.addSublayer(sublayer)

    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }
}

但结果太可怕了

enter image description here

我使用此代码尝试设置动画:

let anim = CABasicAnimation(keyPath: "squareColor")
anim.fromValue = UIColor.blue
anim.toValue = UIColor.red
anim.duration = 1
square.layer.add(anim, forKey: nil)

但是没有任何事情发生。

我很困惑。这样做的正确方法是什么?

编辑:

darker方法来自名为SwiftyColor的可可荚。这就是它的实现方式:

// in an extension of UIColor
public func darker(amount: CGFloat = 0.25) -> UIColor {
    return hueColor(withBrightnessAmount: 1 - amount)
}

private func hueColor(withBrightnessAmount amount: CGFloat) -> UIColor {
    var hue: CGFloat = 0
    var saturation: CGFloat = 0
    var brightness: CGFloat = 0
    var alpha: CGFloat = 0

    if getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
        return UIColor(hue: hue, saturation: saturation,
                           brightness: brightness * amount,
                           alpha: alpha)
    }
    return self
}

6 个答案:

答案 0 :(得分:2)

您使用- -5开启了正确的轨道。

  1. CABasicAnimation中的keyPath:应为let anim = CABasicAnimation(keyPath: "squareColor")

  2. backgroundColoranim.fromValue需要anim.toValue个值(因为您在使用CGColor的{​​{1}}上操作。您可以在此处使用CALayer

  3. 请确保此动画不会为您保留颜色更改,因此您可能需要手动更改该属性。

答案 1 :(得分:1)

使用CAKeyframeAnimation您需要为backgroundColor属性设置动画:

class SquareView: UIView
{
    let sublayer = CAShapeLayer()

    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
        let frame = self.frame
        let strokeWidth = self.frame.width / 8
        sublayer.frame = self.bounds

        let path = UIBezierPath()
        path.move(to: CGPoint(x: frame.width  - strokeWidth / 2 , y: 0))
        path.addLine(to: CGPoint(x: frame.width - strokeWidth / 2, y: frame.height - strokeWidth / 2))
        path.addLine(to: CGPoint(x: 0, y: frame.height - strokeWidth / 2))
        path.addLine(to: CGPoint(x: 0 , y: 0))
        path.lineWidth = strokeWidth
        sublayer.path = path.cgPath
        sublayer.fillColor = UIColor.blue.cgColor
        sublayer.lineWidth = strokeWidth
        sublayer.backgroundColor = UIColor.blue.cgColor
        layer.addSublayer(sublayer)
    }

    //Action
    func animateIt()
    {
        let animateToColor = UIColor(cgColor: sublayer.backgroundColor!).darker().cgColor
        animateColorChange(mLayer: self.sublayer, colors: [sublayer.backgroundColor!, animateToColor], duration: 1)
    }

    func animateColorChange(mLayer:CALayer ,colors:[CGColor], duration:CFTimeInterval)
    {
        let animation = CAKeyframeAnimation(keyPath: "backgroundColor")
        CATransaction.begin()
        animation.keyTimes = [0.2, 0.5, 0.7, 1]
        animation.values = colors
        animation.calculationMode = kCAAnimationPaced
        animation.fillMode = kCAFillModeForwards
        animation.isRemovedOnCompletion = false
        animation.repeatCount = 0
        animation.duration = duration
        mLayer.add(animation, forKey: nil)
        CATransaction.commit()
    }
}

enter image description here

注意:我编辑了答案,以便形状始终适合从故事板添加的父视图

答案 2 :(得分:1)

目标-C

view.backgroundColor = [UIColor whiteColor].CGColor;

[UIView animateWithDuration:2.0 animations:^{
    view.layer.backgroundColor = [UIColor greenColor].CGColor;
} completion:NULL];

夫特

view.backgroundColor = UIColor.white.cgColor
UIView.animate(withDuration: 2.0) { 
    view.backgroundColor = UIColor.green.cgColor
}

答案 3 :(得分:1)

你可以用半透明的黑色来抚摸L形吗?

然后你不必乱搞重绘和颜色计算,你可以使用基本的UIView.animate()

在“LShadow”下方添加到SquareView上,因此您可以按照惯例设置SquareView背景颜色,使用UIView.animate()

ViewController.swift

import UIKit

class ViewController: UIViewController {
    var squareView: SquareView!

    override func viewDidLoad() {
        super.viewDidLoad()

        squareView = SquareView.init(frame: CGRect(x:100, y:100, width:200, height:200))
        self.view.addSubview(squareView)

        squareView.backgroundColor = UIColor.blue
        UIView.animate(withDuration: 2.0, delay: 1.0, animations: {
            self.squareView.backgroundColor = UIColor.red
        }, completion:nil)
    }
}

SquareView.swift

import UIKit

class SquareView: UIView {
    func setupView() {
        let shadow = LShadow.init(frame: self.bounds)
        self.addSubview(shadow)
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }
}


class LShadow: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.clear
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.backgroundColor = UIColor.clear
    }
    override func draw(_ rect: CGRect) {
        let strokeWidth = self.frame.size.width / 8
        let path = UIBezierPath()
        path.move(to: CGPoint(x: self.frame.size.width - strokeWidth / 2, y: 0))
        path.addLine(to: CGPoint(x: self.frame.size.width - strokeWidth / 2, y: self.frame.size.height - strokeWidth / 2))
        path.addLine(to: CGPoint(x: 0, y: self.frame.size.height - strokeWidth / 2))
        UIColor.init(white: 0, alpha: 0.5).setStroke()
        path.lineWidth = strokeWidth
        path.stroke()
    }
}

答案 4 :(得分:1)

我们在下面的代码中有两个不同的CAShapeLayer,前景和背景。背景是绘制到实际开始倒L的程度的矩形路径。 L是使用简单的UIBezierPath几何创建的路径。在设置颜色后,我将每个图层的更改设置为fillColor。

class SquareViewLayers: UIView, CAAnimationDelegate {

    var color: UIColor = UIColor.blue {
        didSet {
            animate(layer: backgroundLayer,
                    from: oldValue,
                    to: color)
            animate(layer: foregroundLayer,
                    from: oldValue.darker(),
                    to: color.darker())
        }
    }

    let backgroundLayer = CAShapeLayer()
    let foregroundLayer = CAShapeLayer()

    func setupView() {

        foregroundLayer.frame = frame
        layer.addSublayer(foregroundLayer)

        backgroundLayer.frame = frame
        layer.addSublayer(backgroundLayer)


        let strokeWidth = width / 8

        let backgroundPathRect = CGRect(origin: .zero, size: CGSize(width: width - strokeWidth,
                                                                    height: height - strokeWidth))
        let backgroundPath = UIBezierPath(rect: backgroundPathRect)
        backgroundLayer.fillColor = color.cgColor
        backgroundLayer.path = backgroundPath.cgPath

        let foregroundPath = UIBezierPath()
        foregroundPath.move(to: CGPoint(x: backgroundPathRect.width,
                                        y: 0))
        foregroundPath.addLine(to: CGPoint(x: width,
                                           y: 0))
        foregroundPath.addLine(to: CGPoint(x: width, y: height))
        foregroundPath.addLine(to: CGPoint(x: 0,
                                           y: height))
        foregroundPath.addLine(to: CGPoint(x: 0,
                                           y: backgroundPathRect.height))
        foregroundPath.addLine(to: CGPoint(x: backgroundPathRect.width,
                                           y: backgroundPathRect.height))
        foregroundPath.addLine(to: CGPoint(x: backgroundPathRect.width,
                                           y: 0))
        foregroundPath.close()

        foregroundLayer.path = foregroundPath.cgPath
        foregroundLayer.fillColor = color.darker().cgColor
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }

    func animate(layer: CAShapeLayer,
                 from: UIColor,
                 to: UIColor) {
        let fillColorAnimation = CABasicAnimation(keyPath: "fillColor")
        fillColorAnimation.fromValue = from.cgColor
        layer.fillColor = to.cgColor
        fillColorAnimation.duration = 1.0
        layer.add(fillColorAnimation,
                  forKey: nil)
    }
}

这是结果,

enter image description here

答案 5 :(得分:1)

我实现了使用动画更改视图的背景颜色,并在源代码中进行了一些调整,以使用CAShapeLayer添加倒L形状。我实现了所需的动画。这是视图的样本。

class SquareView: UIView, CAAnimationDelegate {

    fileprivate func setup() {
        self.layer.addSublayer(self.sublayer)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setup
    }

    let sublayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setup()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        let strokeWidth = self.bounds.width / 8
        var frame = self.bounds
        frame.size.width -= strokeWidth
        frame.size.height -= strokeWidth
        self.sublayer.path = UIBezierPath(rect: frame).cgPath
    }



    var color : UIColor = UIColor.clear {
        willSet {
            self.sublayer.fillColor = newValue.cgColor
            self.backgroundColor = newValue.darker()
        }
    }



    func animation(color: UIColor, duration: CFTimeInterval = 1.0) {
        let animation = CABasicAnimation()
        animation.keyPath = "backgroundColor"
        animation.fillMode = kCAFillModeForwards
        animation.duration = duration
        animation.repeatCount = 1
        animation.autoreverses = false
        animation.fromValue = self.backgroundColor?.cgColor
        animation.toValue = color.darker().cgColor
        animation.delegate = self;

        let shapeAnimation = CABasicAnimation()
        shapeAnimation.keyPath = "fillColor"
        shapeAnimation.fillMode = kCAFillModeForwards
        shapeAnimation.duration = duration
        shapeAnimation.repeatCount = 1
        shapeAnimation.autoreverses = false
        shapeAnimation.fromValue = self.sublayer.fillColor
        shapeAnimation.toValue = color.cgColor
        shapeAnimation.delegate = self;


        self.layer.add(animation, forKey: "kAnimation")
        self.sublayer.add(shapeAnimation, forKey: "kAnimation")
        self.color = color
    }



    public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        self.layer.removeAnimation(forKey: "kAnimation")
        self.sublayer.removeAnimation(forKey: "kAnimation")
    }
}

您可以使用方法animation(color: duration:)在所需的动画持续时间内使用动画更改颜色。

示例

let view = SquareView(frame: CGRect(x: 10, y: 100, width: 200, height: 300))
view.color = UIColor.blue
view.animation(color: UIColor.red) 

以下是结果

enter image description here