为CALayer的shadowPath设置动画时的平滑动画

时间:2015-02-28 21:55:05

标签: ios objective-c animation uikit core-animation

在我的应用中,我有UIView正在使用CALayer来实现阴影:

@implementation MyUIView

    - (instancetype) initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if(!self) return self;

        self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:.2].CGColor;
        self.layer.shadowOffset = CGSizeMake(0, 2);
        self.layer.shadowOpacity = 1;
        self.layer.shadowRadius = 1;

        return self;
    }

@end

如果我想要任何接近合理性能的内容,我必须定义CALayer' s shadowPath

@implementation MyUIView

    - (void) setFrame:(CGRect)frame {
        [super setFrame:frame];

        self.layer.shadowPath = CGPathCreateWithRect(self.bounds, NULL);
    }

@end

每当我为UIView设置动画时,我都注意到了两件事:

  1. 如果我使用shadowPath,阴影会随着旋转和帧大小的变化而动画效果很好。这里需要注意的是动画非常慢,而且表现普遍不足。

  2. 如果我执行,只要shadowPath动画处理UIView,动画就会流畅而及时,但阴影的过渡本身就很多比没有shadowPath更像块(和更不平滑)。

  3. 示例:

    修改

    值得注意的是,这些动画是隐含的 - 我自己并没有调用它们。它们是UIViewController随设备方向旋转的结果。阴影位于UIView上,在旋转期间会改变大小。

2 个答案:

答案 0 :(得分:4)

我尝试重现您提供的两个GIF中显示的行为,但没有成功(也许您可以使用动画代码编辑您的问题,例如UIView animateWithDuration:animations:)。

然而,在我脑海中的某个地方,我记得有一段时间我遇到了类似的问题,结果发现我必须光栅化视图以使其平滑。

所以我无法保证它能为您解决问题,但请试一试:

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [[UIScreen mainScreen] scale];

答案 1 :(得分:0)

在调整视图大小时,shadowPath 需要设置额外的动画。

您可以直接使用我的课程。

/*
 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