垂直进度条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
相关产品推荐
相关产品推荐

