将渐变应用于CAShapeLayer

37
有人有将渐变应用于CAShapeLayer的经验吗?CAShapeLayer是一个非常好用的层类,但它似乎只支持纯色填充,而我想要它具有渐变填充(实际上是可动画化的渐变)。
关于CAShapeLayer的所有其他内容(阴影、形状、描边颜色、可动画化的形状路径),都很棒。
我尝试将CAGradientLayer放置在CAShapeLayer中,或者将CAShapeLayer设置为GradientLayer的遮罩,并将两者添加到容器层中,但这些都没有产生正确的结果。
我应该子类化CAShapeLayer,还是有更好的方法?谢谢。

我相信这个问题包含了Matt Long的答案 - Palimondo
对于任何通过谷歌搜索到这个优秀的旧问题的人,这里是完整、详细的解释,说明这个问题的工作原理:https://dev59.com/qaXja4cB1Zd3GeqPYevT#57525960 - Fattie
3个回答

65
您可以使用您的形状路径创建一个蒙版层,并将其应用于渐变层,像这样:
UIView *v = [[UIView alloc] initWithFrame:self.window.frame];

CAShapeLayer *gradientMask = [CAShapeLayer layer];   
gradientMask.fillColor = [[UIColor clearColor] CGColor];
gradientMask.strokeColor = [[UIColor blackColor] CGColor];
gradientMask.lineWidth = 4;
gradientMask.frame = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.height);

CGMutablePathRef t = CGPathCreateMutable();    
CGPathMoveToPoint(t, NULL, 0, 0);
CGPathAddLineToPoint(t, NULL, v.bounds.size.width, v.bounds.size.height);

gradientMask.path = t;


CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5,1.0);
gradientLayer.endPoint = CGPointMake(0.5,0.0);
gradientLayer.frame = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.height);
NSMutableArray *colors = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
    [colors addObject:(id)[[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1] CGColor]];
}
gradientLayer.colors = colors;

[gradientLayer setMask:gradientMask];
[v.layer addSublayer:gradientLayer];

如果您想使用阴影,您需要在渐变图层下方放置一个形状图层的“副本”,重复使用相同的路径引用。

3
这个回答很棒。对于不知道的人(就像我之前一样),我想补充说,你可以将遮罩、渐变、阴影和其他任何需要的图层添加到一个容器图层中(CALayer container = [CALayer layer]),然后只需管理该容器图层,如果需要动画其位置。 - Jonathan Dumaine
非常好的答案!谢谢! - 思齐省身躬行

12

感谢@Palimondo提供的精彩答案!

如果有人正在寻找此解决方案的Swift 4 + 填充动画代码:

    let myView = UIView(frame: .init(x: 0, y: 0, width: 200, height: 150))
    view.addSubview(myView)
    myView.center = view.center

    // Start and finish point
    let startPoint = CGPoint(x: myView.bounds.minX, y: myView.bounds.midY)
    let finishPoint = CGPoint(x: myView.bounds.maxX, y: myView.bounds.midY)

    // Path
    let path = UIBezierPath()
    path.move(to: startPoint)
    path.addLine(to: finishPoint)

    // Gradient Mask
    let gradientMask = CAShapeLayer()
    let lineHeight = myView.frame.height
    gradientMask.fillColor = UIColor.clear.cgColor
    gradientMask.strokeColor = UIColor.black.cgColor
    gradientMask.lineWidth = lineHeight
    gradientMask.frame = myView.bounds
    gradientMask.path = path.cgPath

    // Gradient Layer
    let gradientLayer = CAGradientLayer()
    gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
    gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)

    // make sure to use .cgColor
    gradientLayer.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
    gradientLayer.frame = myView.bounds
    gradientLayer.mask = gradientMask

    myView.layer.addSublayer(gradientLayer)

    // Corner radius
    myView.layer.cornerRadius = 10
    myView.clipsToBounds = true

额外内容。如果您需要“填充动画”,请添加以下行:

    // Filling animation
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0
    animation.duration = 10
    gradientMask.add(animation, forKey: "LineAnimation")

3
这是一个很好的解决方案,但如果您在创建CAShapeLayer类别时没有立即查看视图,则可能会遇到意外问题。
请参见设置新创建的CAShapeLayer的正确框架 底线是获取路径的边界,然后使用路径边界和必要的平移设置渐变掩膜的框架。好处在于通过使用路径的边界而不是任何其他框架,渐变只会适合路径边界(假设这是您想要的)。
// Category on CAShapeLayer

CGRect pathBounds = CGPathGetBoundingBox(self.path);

CAShapeLayer *gradientMask = [CAShapeLayer layer];
gradientMask.fillColor = [[UIColor blackColor] CGColor];
gradientMask.frame = CGRectMake(0, 0, pathBounds.size.width, pathBounds.size.height);
gradientMask.path = self.path;

CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5,1.0);
gradientLayer.endPoint = CGPointMake(0.5,0.0);
gradientLayer.frame = CGRectMake(0, 0, pathBounds.size.width, pathBounds.size.height);

NSMutableArray *colors = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
    [colors addObject:(id)[[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1] CGColor]];
}
gradientLayer.colors = colors;

[gradientLayer setMask:gradientMask];
[self addSublayer:gradientLayer];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接