动画CALayer的shadowPath属性

时间:2011-05-07 23:39:45

标签: ios animation calayer shadow

我知道CALayer的shadowPath只能使用显式动画制作动画,但我仍然无法使其工作。我怀疑我没有正确传递toValue - 据我所知,这必须是id,但该属性需要一个CGPathRef。将其存储在UIBezierPath中似乎不起作用。我使用以下代码进行测试:

CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
theAnimation.duration = 3.0;
theAnimation.toValue = [UIBezierPath bezierPathWithRect:CGRectMake(-10.0, -10.0, 50.0, 50.0)];
[self.view.layer addAnimation:theAnimation forKey:@"animateShadowPath"];

(我使用负值,以确保阴影延伸到位于其顶部的视图之外......图层的masksToBounds属性设置为NO)。

如何实现shadowPath的动画?

更新

问题几乎解决了。不幸的是,主要问题是有点粗心的错误......

我犯的错误是将动画添加到视图控制器的根层,而不是我专用于阴影的图层。另外,@ pe8ter是正确的,因为toValue需要CGPathRef强制转换为id(显然当我尝试过此操作之前,由于错误的图层错误,我仍然没有动画) 。动画使用以下代码:

CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
theAnimation.duration = 3.0;
theAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:myRect].CGPath;
[controller.shadowLayer addAnimation:theAnimation forKey:@"shadowPath"];

我很欣赏我提供的示例代码很难发现。希望它仍然可以用于处于类似情况的人。

然而,当我尝试添加行

controller.shadowLayer.shadowPath = [UIBezierPath bezierPathWithRect:myRect].CGPath;

动画停止工作,阴影立即跳到最终位置。文档说添加动画时使用与要更改的属性相同的键,以覆盖设置属性值时创建的隐式动画,但是shadowPath无法生成隐式动画...所以如何获取新属性 动画之后

4 个答案:

答案 0 :(得分:9)

首先,您没有设置动画的fromValue

其次,你是对的:toValue接受CGPathRef,但需要将其转换为id。做这样的事情:

theAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:newRect].CGPath;

如果您希望在动画之后保留更改,则还需要明确设置图层的shadowPath属性。

答案 1 :(得分:1)

let cornerRadious = 10.0
        //
        let shadowPathFrom = UIBezierPath(roundedRect: rect1, cornerRadius: cornerRadious)
        let shadowPathTo = UIBezierPath(roundedRect: rect2, cornerRadius: cornerRadious)
        //
        layer.masksToBounds = false
        layer.shadowColor = UIColor.yellowColor().CGColor
        layer.shadowOpacity = 0.6
        //
        let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
        shadowAnimation.fromValue = shadowPathFrom.CGPath
        shadowAnimation.toValue = shadowPathTo.CGPath
        shadowAnimation.duration = 0.4
        shadowAnimation.autoreverses = true
        shadowAnimation.removedOnCompletion = true
        shadowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        layer.addAnimation(shadowAnimation, forKey: "shadowAnimation")

答案 2 :(得分:0)

我在阴影动画方面遇到了同样的问题,并找到了在动画结束后持久保留阴影的解决方案。开始动画之前,您需要为阴影层设置最终路径。之所以会恢复初始阴影,是因为CoreAnimation不会更新原始图层的属性,而是会创建该图层的副本并在此副本上显示动画(check Neko1kat answer for more details)。动画结束后,系统删除该动画层并返回原始层,该层尚未更新路径,并且会出现旧的阴影。尝试以下代码:

    let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
    shadowAnimation.fromValue = currentShadowPath
    shadowAnimation.toValue = newShadowPath
    shadowAnimation.duration = 3.0

    shadowLayer.shadowPath = newShadowPath
    shadowLayer.add(shadowAnimation, forKey: "shadowAnimation")

答案 3 :(得分:0)

虽然不直接回答这个问题,但是如果你只是需要一个view可以投下阴影,可以直接使用我的类。

/*
 Shadow.swift

 Copyright © 2018, 2020-2021 BB9z
 https://github.com/BB9z/iOS-Project-Template

 The MIT License
 https://opensource.org/licenses/MIT
 */

/**
 A view drops shadow.
 */
@IBDesignable
class ShadowView: UIView {
    @IBInspectable var shadowOffset: CGPoint = CGPoint(x: 0, y: 8) {
        didSet { needsUpdateStyle = true }
    }
    @IBInspectable var shadowBlur: CGFloat = 10 {
        didSet { needsUpdateStyle = true }
    }
    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet { needsUpdateStyle = true }
    }
    /// Set nil can disable shadow
    @IBInspectable var shadowColor: UIColor? = UIColor.black.withAlphaComponent(0.3) {
        didSet { needsUpdateStyle = true }
    }
    @IBInspectable var cornerRadius: CGFloat {
        get { layer.cornerRadius }
        set { layer.cornerRadius = newValue }
    }

    private var needsUpdateStyle = false {
        didSet {
            guard needsUpdateStyle, !oldValue else { return }
            DispatchQueue.main.async { [self] in
                if needsUpdateStyle { updateLayerStyle() }
            }
        }
    }

    private func updateLayerStyle() {
        needsUpdateStyle = false
        if let color = shadowColor {
            Shadow(view: self, offset: shadowOffset, blur: shadowBlur, spread: shadowSpread, color: color, cornerRadius: cornerRadius)
        } else {
            layer.shadowColor = nil
            layer.shadowPath = nil
            layer.shadowOpacity = 0
        }
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        updateLayerStyle()
    }

    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
        lastLayerSize = layer.bounds.size
        if shadowColor != nil, layer.shadowOpacity == 0 {
            updateLayerStyle()
        }
    }

    private var lastLayerSize = CGSize.zero {
        didSet {
            if oldValue == lastLayerSize { return }
            guard shadowColor != nil else { return }
            updateShadowPathWithAnimationFixes(bonuds: layer.bounds)
        }
    }

    // We needs some additional step to achieve smooth result when view resizing
    private func updateShadowPathWithAnimationFixes(bonuds: CGRect) {
        let rect = bonuds.insetBy(dx: shadowSpread, dy: shadowSpread)
        let newShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
        if let resizeAnimation = layer.animation(forKey: "bounds.size") {
            let key = #keyPath(CALayer.shadowPath)
            let shadowAnimation = CABasicAnimation(keyPath: key)
            shadowAnimation.duration = resizeAnimation.duration
            shadowAnimation.timingFunction = resizeAnimation.timingFunction
            shadowAnimation.fromValue = layer.shadowPath
            shadowAnimation.toValue = newShadowPath
            layer.add(shadowAnimation, forKey: key)
        }
        layer.shadowPath = newShadowPath
    }
}

/**
 Make shadow with the same effect as Sketch app.
 */
func Shadow(view: UIView?, offset: CGPoint, blur: CGFloat, spread: CGFloat, color: UIColor, cornerRadius: CGFloat = 0) {  // swiftlint:disable:this identifier_name
    guard let layer = view?.layer else {
        return
    }
    layer.shadowColor = color.cgColor
    layer.shadowOffset = CGSize(width: offset.x, height: offset.y)
    layer.shadowRadius = blur
    layer.shadowOpacity = 1
    layer.cornerRadius = cornerRadius

    let rect = layer.bounds.insetBy(dx: spread, dy: spread)
    layer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
}

通过https://github.com/BB9z/iOS-Project-Template/blob/master/App/General/Effect/Shadow.swift