【问题标题】:How to draw radial gradients in a CALayer?如何在 CALayer 中绘制径向渐变?
【发布时间】:2015-01-10 12:11:43
【问题描述】:

我知道CAGradientLayer 目前不支持径向渐变,只有kCAGradientLayerAxial 选项。

我想要类似下面的东西:

我已经四处寻找这个问题,发现有办法解决这个问题。但这些解释对我来说不是很清楚。所以我想知道我是否可以使用CAGradientLayer 绘制径向渐变,如果可以,那该怎么做?

【问题讨论】:

  • 为什么不能使用CGContextDrawRadialGradient?您始终可以渲染到图像,然后将其分配给CALayer 的内容。使用CALayerrenderInContext: 也是一种选择,但这一切都会导致CGContextDrawRadialGradient
  • @Mazyod 我在 UIView 中添加了多个图层作为子图层,我正在这些图层上绘制 bezierPath。所以像实体填充、笔画宽度等所有属性都与图层相关联。所以只想保持代码的一致性。
  • @Mazyod 另外,我没有理解您渲染图像然后将其分配给 CALayer 内容的概念。能否请您详细说明一下。

标签: ios objective-c calayer quartz-core


【解决方案1】:

斯威夫特 5

import UIKit

final class RadialGradientLayer: CALayer {

    let centerColor: CGColor
    let edgeColor: CGColor

    init(centerColor: UIColor, edgeColor: UIColor) {
        self.centerColor = centerColor.cgColor
        self.edgeColor = edgeColor.cgColor
        super.init()
        needsDisplayOnBoundsChange = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(in ctx: CGContext) {
        ctx.saveGState()
        let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
                                  colors: [centerColor, edgeColor] as CFArray,
                                  locations: [0.0, 1.0])

        let gradCenter = CGPoint(x: bounds.midX, y: bounds.midY)
        let gradRadius = min(bounds.width, bounds.height)

        ctx.drawRadialGradient(gradient!,
                               startCenter: gradCenter,
                               startRadius: 0.0,
                               endCenter: gradCenter,
                               endRadius: gradRadius,
                               options: .drawsAfterEndLocation)
    }
}

【讨论】:

    【解决方案2】:

    我不知道什么时候,但现在你可以将 CAGradientLayertype 更改为 kCAGradientLayerRadial 并且它可以工作。

    理论上Core Animation方式的性能要优于Core Graphics。

    一些示例代码:

    class View : UIView {
    
        let gradientLayer = CAGradientLayer()
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
            gradientLayer.endPoint = CGPoint(x: 1, y: 1)
            gradientLayer.locations = [
                NSNumber(value: 0.6),
                NSNumber(value: 0.8)
            ]
            gradientLayer.type = .radial
            gradientLayer.colors = [
                UIColor.green.cgColor,
                UIColor.purple.cgColor,
                UIColor.orange.cgColor,
                UIColor.red.cgColor
            ]
            self.layer.addSublayer(gradientLayer)
        }
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override func layoutSublayers(of layer: CALayer) {
            assert(layer === self.layer)
            gradientLayer.frame = layer.bounds
        }
    }
    

    【讨论】:

    • 完美运行。但是位置(0.6 和 0.8)和起点/终点是什么意思?
    • 好多了。感谢您添加此内容。甚至不会检查它。
    【解决方案3】:

    据我了解,您只需要一个绘制渐变的图层,CGContextDrawRadialGradient 就可以完美地满足这一需求。重申一下你所说的,CAGradientLayer 不支持径向渐变,我们对此无能为力,除了可以使用 CALayer 子类干净地完成的不必要的混合。

    注意:渐变绘制代码是taken from here。这不是这个答案的内容。


    viewDidLoad:

    GradientLayer *gradientLayer = [[GradientLayer alloc] init];
    gradientLayer.frame = self.view.bounds;
    
    [self.view.layer addSublayer:gradientLayer];
    

    CALayer 子类:

    - (instancetype)init
    {
        self = [super init];
        if (self) {
            [self setNeedsDisplay];
        }
        return self;
    }
    
    - (void)drawInContext:(CGContextRef)ctx
    {
    
        size_t gradLocationsNum = 2;
        CGFloat gradLocations[2] = {0.0f, 1.0f};
        CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.5f};
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
        CGColorSpaceRelease(colorSpace);
    
        CGPoint gradCenter= CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
        CGFloat gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;
    
        CGContextDrawRadialGradient (ctx, gradient, gradCenter, 0, gradCenter, gradRadius, kCGGradientDrawsAfterEndLocation);
    
    
        CGGradientRelease(gradient);
    }
    

    【讨论】:

    • 如何在 SWIFT 中实现这一点?
    • @DrPatience 上面的代码直接适用于swift。就像override func drawInContext(ctx: CGContext!) { …
    • 谢谢@AndyPoes 我在这行遇到问题>> "CGContextDrawRadialGradient(ctx, gradient, gradCenter, 0.0, gradCenter, gradRadius, kCGGradientDrawsAfterEndLocation)" 和实例方法
    • @DrPatience 不确定您遇到的具体问题,但也许这会有所帮助? gist.github.com/MrBendel/f10d46a3160ffdf28989
    • @AndyPoes 太棒了,非常感谢:)。 swift中是否也需要init方法?如果是的话,是否有重大变化,谢谢。
    【解决方案4】:

    这是我使用的 Swift3 代码:

    import UIKit
    
    class RadialGradientLayer: CALayer {
    
        required override init() {
            super.init()
            needsDisplayOnBoundsChange = true
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        required override init(layer: Any) {
            super.init(layer: layer)
        }
    
        public var colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
    
        override func draw(in ctx: CGContext) {
            ctx.saveGState()
    
            let colorSpace = CGColorSpaceCreateDeviceRGB()
            var locations = [CGFloat]()
            for i in 0...colors.count-1 {
                locations.append(CGFloat(i) / CGFloat(colors.count))
            }
            let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations)
            let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
            let radius = min(bounds.width / 2.0, bounds.height / 2.0)
            ctx.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))
        }
    }
    

    【讨论】:

    • 是的,这对我有用。几点注意事项: - 如果您在混合和匹配项目(Obj-C + Swift)中编码,定义 CGColors 数组将引发错误,因为 Objective-C 不允许您创建原始数据类型数组.为了克服这个问题,您可以创建一个 UIColors 数组,并在 draw 函数中将它们转换为它们的 cgColor 值。 - 当添加到较小的子视图时,此绘制的圆圈具有锯齿状边缘。我通过设置图层的masksToBounds = true 解决了这个问题,然后添加了一个圆角半径以使包含图层的视图成为圆形,使边缘平滑。
    • 这对我来说也很有效——我唯一改变的是使用max而不是min作为半径并使用.drawsAfterEndLocation而不是CGGradientDrawingOptions(rawValue: 0),因为这最终导致了更多令人满意的结果(恕我直言)
    • 您可以像这样添加您创建的径向CALayerview.layer.insertSublayer(gradientLayer, below: view.layer)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-06
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多