【问题标题】:How to animate shadow with gradient?如何用渐变为阴影设置动画?
【发布时间】:2017-02-25 07:28:11
【问题描述】:

我有如下图表:

我对动画和阴影有疑问。 我用动画绘制了一个渐变,但从一开始我就有了我不想要的阴影和遮罩层,我想要用渐变动画的阴影。 当前带有动画的图表如下所示。

我不希望用户从一开始就看到阴影和遮罩层。

这是我的代码:

import Foundation
import UIKit

@IBDesignable class CircularProgressView: UIView {

@IBInspectable var containerCircleColor: UIColor = UIColor.lightGray
@IBInspectable var gradientStartColor: UIColor = UIColor.green
@IBInspectable var gradientEndColor: UIColor = UIColor.yellow
@IBInspectable var arcWidth: CGFloat = 20


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

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


fileprivate func circularProgressView_init() {

    let viewHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
    self.addConstraint(viewHeight)

}

override func prepareForInterfaceBuilder() {
    circularProgressView_init()
}

override func draw(_ rect: CGRect) {
    let width = self.bounds.width
    let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
    let radius: CGFloat = (width - (arcWidth * 2.5)) / 2
    let progressStartAngle: CGFloat = 3 * CGFloat.pi / 2
    let progressEndAngle: CGFloat = CGFloat.pi / 2

    //fill circular
    let circlePath = UIBezierPath(arcCenter:  center,
                                  radius: radius,
                                  startAngle: 0,
                                  endAngle: 360,
                                  clockwise: true)
    circlePath.lineWidth = arcWidth
    containerCircleColor.setStroke()
    circlePath.stroke()


    //MARK: ProgressPath
    let progressPath = UIBezierPath(arcCenter: center,
                                    radius: radius,
                                    startAngle: progressStartAngle,
                                    endAngle: progressEndAngle,
                                    clockwise: true)
    progressPath.lineWidth = arcWidth
    progressPath.lineCapStyle = .round

    //MARK: Gradient
    let gradientLayer = CAGradientLayer()
    gradientLayer.colors = [gradientStartColor.cgColor , gradientEndColor.cgColor]
    gradientLayer.startPoint = CGPoint(x: 0, y: 0)
    gradientLayer.endPoint = CGPoint(x:1, y:1)
    gradientLayer.frame = self.bounds

    //MARK: Animation
    let anim = CABasicAnimation(keyPath: "strokeEnd")
    anim.duration = 2
    anim.fillMode = kCAFillModeForwards
    anim.fromValue = 0
    anim.toValue = 1

    //MARK: Mask Layer
    let maskLayer = CAShapeLayer()
    maskLayer.path = progressPath.cgPath
    maskLayer.fillColor = UIColor.clear.cgColor
    maskLayer.strokeColor = UIColor.black.cgColor
    maskLayer.lineWidth = arcWidth
    maskLayer.lineCap = kCALineCapRound

    gradientLayer.mask = maskLayer

    self.layer.insertSublayer(gradientLayer, at: 0)

    let context = UIGraphicsGetCurrentContext()
    let shadow = UIColor.lightGray
    let shadowOffset = CGSize(width: 3.1, height: 3.1)
    let shadowBlurRadius: CGFloat = 5
    context!.saveGState()
    context!.setShadow(offset: shadowOffset, blur: shadowBlurRadius,  color: (shadow as UIColor).cgColor)
    progressPath.stroke()
    context?.restoreGState()

    maskLayer.add(anim, forKey: nil)
    gradientLayer.add(anim, forKey: nil)

  }
}

有可能吗? 如果不是,我怎么能至少隐藏阴影和蒙版并在动画结束后显示它?

【问题讨论】:

    标签: swift animation gradient shadow


    【解决方案1】:

    您应该创建另一个图层来提供阴影。

    1 - 首先你必须创建一个 UIView 作为shadowLayer

    2 - 然后使用与您已经创建的maskLayer(在您的代码中)相同的路径和层来掩盖这个shadowLayer。例如:shadowMaskLayer

    3 - 将阴影属性添加到这个新的shadowMaskLayer

    4 - 然后将 shadowLayer 添加到原始 UIView CircularProgressView

    5 - 还将您已经拥有的动画添加到此shadowLayer 以使用整个圆圈为阴影设置动画。

    不要犹豫,问问题;)

    【讨论】:

    • 谢谢,这是个好主意。我将更改我的代码并分享结果。
    【解决方案2】:

    好吧,我把正确答案放在这里,也许将来有人需要它。

    import Foundation
    import UIKit
    
    @IBDesignable class CircularProgressView: UIView {
    
    @IBInspectable var containerCircleColor: UIColor = UIColor.lightGray
    @IBInspectable var gradientStartColor: UIColor = UIColor.yellow
    @IBInspectable var gradientEndColor: UIColor = UIColor.red
    @IBInspectable var arcWidth: CGFloat = 20
    @IBInspectable var progressPercent: CGFloat = 50
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        circularProgressView_init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        circularProgressView_init()
    }
    
    fileprivate func circularProgressView_init() {
        let viewHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
        self.addConstraint(viewHeight)
    }
    
    override func prepareForInterfaceBuilder() {
        circularProgressView_init()
    }
    
    override func draw(_ rect: CGRect) {
        let width = self.bounds.width
        let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        let radius: CGFloat = (width - (arcWidth * 2.5)) / 2
        let progressStartAngle: CGFloat = 3 * CGFloat.pi / 2
        let progressEndAngle: CGFloat = ConvertToTrigonometry.shared.trigonimetryCordinate(percentage: progressPercent) //CGFloat.pi / 2
    
        //fill circular
        let circlePath = UIBezierPath(arcCenter:  center,
                                      radius: radius,
                                      startAngle: 0,
                                      endAngle: 360,
                                      clockwise: true)
        circlePath.lineWidth = arcWidth
        containerCircleColor.setStroke()
        circlePath.stroke()
    
    
        //MARK: ProgressPath
        let progressPath = UIBezierPath(arcCenter: center,
                                        radius: radius,
                                        startAngle: progressStartAngle,
                                        endAngle: progressEndAngle,
                                        clockwise: true)
        progressPath.lineWidth = arcWidth
        progressPath.lineCapStyle = .round
    
        //MARK: Gradient
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [gradientStartColor.cgColor , gradientEndColor.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x:1, y:1)
        gradientLayer.frame = self.bounds
    
        //MARK: Mask Layer
        let maskLayer = CAShapeLayer()
        maskLayer.path = progressPath.cgPath
        maskLayer.fillColor = UIColor.clear.cgColor
        maskLayer.backgroundColor = UIColor.black.cgColor
        maskLayer.strokeColor = UIColor.black.cgColor
        maskLayer.lineWidth = arcWidth
        maskLayer.lineCap = kCALineCapRound
        maskLayer.masksToBounds = false
    
        gradientLayer.mask = maskLayer
    
        //MARK: Shadow
        let shadowLayer = CAShapeLayer()
        shadowLayer.frame = bounds
        shadowLayer.backgroundColor = UIColor.gray.cgColor
        layer.addSublayer(shadowLayer)
    
        let maskShadowLayer = CAShapeLayer()
        maskShadowLayer.path = progressPath.cgPath
        maskShadowLayer.fillColor = UIColor.clear.cgColor
        maskShadowLayer.backgroundColor = UIColor.black.cgColor
        maskShadowLayer.strokeColor = UIColor.black.cgColor
        maskShadowLayer.lineWidth = arcWidth
        maskShadowLayer.lineCap = kCALineCapRound
        maskShadowLayer.masksToBounds = false
        maskShadowLayer.shadowColor = UIColor.black.cgColor
        maskShadowLayer.shadowOpacity = 0.5
        maskShadowLayer.shadowOffset = CGSize(width: 3.1, height: 3.1)
    
        shadowLayer.mask = maskShadowLayer
    
        //MARK: Animation
        let anim = CABasicAnimation(keyPath: "strokeEnd")
        anim.duration = 2
        anim.fillMode = kCAFillModeForwards
        anim.fromValue = 0
        anim.toValue = 1
    
        maskShadowLayer.add(anim, forKey: nil)
        maskLayer.add(anim, forKey: nil)
        gradientLayer.add(anim, forKey: nil)
    
        layer.addSublayer(gradientLayer)
    
     }
    
    }
    

    还有一个辅助类,我用于三角函数转换:

    import Foundation
    import UIKit
    
    class ConvertToTrigonometry {
    
    static let shared = ConvertToTrigonometry()
    
    func trigonimetryCordinate(percentage: CGFloat) -> CGFloat {
        let pi = CGFloat.pi
        let trigonometryRatio = (percentage * 360) / 100 // How much you want to move forward in axis.
        let endPointDegree = (3 * pi / 2) + ((trigonometryRatio * 2 / 360) * pi) // End point on axis based on your trigonometryRatio and the start point which is 3pi/2
        return endPointDegree
     }
    }
    

    此解决方案使用动画渐变和阴影绘制弧线。 你可以在我的Github找到完整的项目。

    【讨论】:

      猜你喜欢
      • 2021-10-05
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 2018-02-04
      • 1970-01-01
      • 1970-01-01
      • 2019-12-04
      相关资源
      最近更新 更多