将渐变应用于 CAShapeLayer

发布于 2024-10-12 22:09:07 字数 321 浏览 8 评论 0原文

有人有将渐变应用于 CAShapeLayer 的经验吗? CAShapeLayer 是一个很棒的图层类,但它似乎只支持纯色填充着色,而我希望它具有渐变填充(实际上是一个可设置动画的渐变)。

与 CAShapeLayer 相关的所有其他内容(阴影、形状、描边颜色、可动画形状路径)都非常棒。

我尝试将 CAGradientLayer 放置在 CAShapeLayer 内,或者确实将 CAShapeLayer 设置为 GradientLayer 的遮罩并将两者添加到容器层中,但这些没有正确的结果。

我应该子类化 CAShapeLayer,还是有更好的方法?

谢谢。

Does anyone have any experience in applying a Gradient to a CAShapeLayer? CAShapeLayer is a fantastic layer class, but it appears to only support solid fill coloring, whereas I'd like it to have a gradient fill (actually an animatable gradient at that).

Everything else to do with CAShapeLayer (shadows, shapes, stroke color, animatable shape path) is fantastic.

I've tried placing a CAGradientLayer inside a CAShapeLayer, or indeed setting the CAShapeLayer as the mask of the GradientLayer and adding both to a container layer, but these don't have the right outcome.

Should I subclass CAShapeLayer, or is there a better way forward?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

和我恋爱吧 2024-10-19 22:09:07

您可以使用形状的路径创建遮罩层并将其应用到渐变层上,如下所示:

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];

如果您还想使用阴影,则必须在渐变层下放置形状层的“副本” ,回收相同的路径引用。

You could use the path of your shape to create a masking layer and apply that on the gradient layer, like this:

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];

If you want to also use the shadows, you would have to place a "duplicate" of the shape layer under the gradient layer, recycling the same path reference.

撩起发的微风 2024-10-19 22:09:07

非常感谢@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")

Many thanks to @Palimondo for a great answer!

In case someone is looking for Swift 4 + filling animation code of this solution:

    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

Extra. In case you also need a "filling animation", add this lines:

    // Filling animation
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0
    animation.duration = 10
    gradientMask.add(animation, forKey: "LineAnimation")
夜未央樱花落 2024-10-19 22:09:07

这是一个很好的解决方案,但如果您在 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];

This is a great solution, but you might encounter unexpected problems if you're creating a category on CAShapeLayer where you don't immediately have the view.

See Setting correct frame of a newly created CAShapeLayer

Bottom line, get the bounds of the path then set the gradient mask's frame using the path bounds and translate as necessary. Good thing here is that by using the path's bounds rather than any other frame, the gradient will only fit within the path bounds (assuming that's what you want).

// 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];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文