You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

垂直进度条CAGradientLayer动画方向问题求助

解决垂直进度条渐变动画从上到下的问题

你的核心问题在于渐变层的方向配置和进度遮罩的布局逻辑不匹配,导致动画方向不符合预期。以下是具体的修改方案:

关键修改点

1. 固定渐变层的垂直方向

确保CAGradientLayer的渐变方向是垂直的(渐变线平行于X轴),需要明确设置startPoint和endPoint为垂直方向:

gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)   // 顶部中点
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)     // 底部中点

这样渐变颜色会沿Y轴方向变化,渐变线保持水平(平行于X轴)。

2. 修正进度遮罩的布局逻辑

原来的progressLayer是从底部往上扩展,逻辑绕且容易出错,改为从顶部开始,高度随进度变化:

let progressRect = CGRect(x: 0, y: 0, width: rect.width, height: rect.height * progress)
progressLayer.frame = progressRect

这样遮罩会显示进度条顶部到当前进度的区域,符合垂直进度条的视觉逻辑。

3. 调整动画的locations范围(可选,但更贴合垂直方向)

虽然原来的locations值可以工作,但调整为更直观的垂直方向范围,动画效果一致:

flowAnimation.fromValue = [-0.3, -0.15, 0]
flowAnimation.toValue = [1, 1.15, 1.3]

(注:这个值本身没问题,因为locations是相对于渐变方向的坐标,垂直方向下就是Y轴的比例)

4. 把Layer布局移到layoutSubviews而非draw

draw(_:)方法是用于CoreGraphics绘制,CALayer的布局应该放在layoutSubviews中,避免布局冲突:

override func layoutSubviews() {
    super.layoutSubviews()
    let rect = bounds
    
    backgroundColor = .lightGray
    
    gradientLayer.frame = rect
    gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
    
    backgroundMask.path = UIBezierPath(roundedRect: rect, cornerRadius: 8).cgPath
    layer.mask = backgroundMask
    
    let progressRect = CGRect(x: 0, y: 0, width: rect.width, height: rect.height * progress)
    progressLayer.frame = progressRect
}

同时移除draw(_:)方法里的布局代码,保留空实现或者直接删掉。

修改后的完整代码

class ProgressBarView: UIView {

    var color: UIColor = .red {
        didSet { updateGradientColors() }
    }

    var gradientColor: UIColor = .white {
        didSet { updateGradientColors() }
    }
    
    var progress: CGFloat = 0 {
        didSet {
            DispatchQueue.main.async { self.layoutSubviews() }
        }
    }

    private let progressLayer = CALayer()
    private let gradientLayer = CAGradientLayer()
    private let backgroundMask = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupLayers()
        createAnimation()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupLayers()
        createAnimation()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        let rect = bounds
        
        backgroundColor = .lightGray
        
        gradientLayer.frame = rect
        
        backgroundMask.path = UIBezierPath(roundedRect: rect, cornerRadius: 8).cgPath
        layer.mask = backgroundMask
        
        let progressRect = CGRect(x: 0, y: 0, width: rect.width, height: rect.height * progress)
        progressLayer.frame = progressRect
    }

    private func setupLayers() {
        layer.addSublayer(gradientLayer)
        
        gradientLayer.mask = progressLayer
        gradientLayer.locations = [0.35, 0.5, 0.65]
        
        // 设置垂直渐变方向
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
        
        progressLayer.backgroundColor = UIColor.black.cgColor
    }
    
    private func updateGradientColors() {
        gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
    }

    private func createAnimation() {
        let flowAnimation = CABasicAnimation(keyPath: "locations")
        flowAnimation.fromValue = [-0.3, -0.15, 0]
        flowAnimation.toValue = [1, 1.15, 1.3]

        flowAnimation.isRemovedOnCompletion = false
        flowAnimation.repeatCount = Float.infinity
        flowAnimation.duration = 1

        gradientLayer.add(flowAnimation, forKey: "flowAnimation")
    }
}

额外说明

  • 新增了updateGradientColors()方法,单独处理渐变颜色更新,逻辑更清晰。
  • 移除了draw(_:)方法,因为所有Layer布局都移到了layoutSubviews,符合UIKit的最佳实践。
  • 进度更新时调用layoutSubviews(),确保界面实时刷新。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 06:50:50