如何从 CAGradientLayers 和 CAShapeLayers 组成 UIButton?

如何解决如何从 CAGradientLayers 和 CAShapeLayers 组成 UIButton?

我想重新创建一个斜角效果圆形 UIButton 子类,如下图所示(但使用用户选择的基色)。我对如何设置 CAGradientLayer/掩蔽 CAShapeLayer 的边界感到困惑。

Goal1

我想像在 SwiftUI 中一样将它组合成几个 CAShapeLayers。这会根据需要进行渲染(无梯度)。

CAShapeLayers

但是,当使用先前渲染的 CAGradientLayer 遮罩 CAShapeLayer 时,渐变不会被明显地绘制。

CAGradientLayer3

我认为我的错误与边界/帧设置有关。当我将子层添加移动到 layoutSubviews() 时,渐变环的一部分被渲染。此外,用户颜色圆圈遵守父 VC 设置的约束,但其他层不遵守。

layoutSubviews

我已经阅读了几篇关于将 CAShapeLayer 作为掩码应用于 {{1} },但我无法理解如何为这些多层正确设置框架。

CAGradientLayer

extension BeveledButton {
    
    /// Draws yellow ring in desired location
    func makeembossmentRingAsShapeLayer()  -> CAShapeLayer {
        let pathFrame = frame.insetBy(dx: insetBevel,dy: insetBevel)
        let path = CGPath.circlestroked(width: bevelRingWidth,inFrame: pathFrame)
        
        let shape = CAShapeLayer()
        shape.path = path
        shape.fillColor = UIColor.yellow.cgColor
        return shape
    }
    
    /// Does not draw gradient (visibly) where yellow ring was
    func makeembossmentRingAsGradientLayer()  -> CAGradientLayer {
        let pathFrame = frame.insetBy(dx: insetBevel,inFrame: frame)
        
        let mask = CAShapeLayer()
        mask.path = path
        mask.fillColor = UIColor.black.cgColor
        mask.linewidth = 4
        mask.frame = pathFrame
        
        let gradient = CAGradientLayer(colors: [UIColor.orange,UIColor.green],in: pathFrame)
        gradient.frame = pathFrame
        gradient.mask = mask
        return gradient
    }
}


extension CGPath {
    
    static func circle(inFrame: CGRect) -> CGPath {
        CGPath(ellipseIn: inFrame,transform: nil)
    }
    
    static func circlestroked(width: CGFloat,inFrame: CGRect) -> CGPath {
        var path = CGPath(ellipseIn: inFrame,transform: nil)
        path = path.copy(strokingWithWidth: width,lineCap: .round,lineJoin: .round,miterLimit: 0)
        return path
    }
}

extension CAGradientLayer {
    
    convenience init(colors: [UIColor],in frame: CGRect) {
        self.init()
        self.colors = colors.map(\.cgColor)
        self.frame = frame
    }
}

解决方法

你走在正确的轨道上......

这个想法是添加4个子层:

  1. “外部”CAShapeLayer
  2. “戒指”CAShapeLayer
  3. 带有 CAGradientLayer 掩码的“斜角”CAShapeLayer
  4. 带有 CAGradientLayer 面具的“内在”CAShapeLayer

将每一层的边框插入上一层的宽度,所以:

  • rect = 边界
  • 外层框架将是矩形
  • 通过“外宽度”插入矩形
  • 环形层框架将是那个插入矩形
  • 通过“环宽度”插入矩形
  • 斜面层框架将是那个插入矩形
  • 按“斜角宽度”插入矩形
  • 内层框架将是那个插入矩形

所以 layoutSubviews 看起来像这样:

override func layoutSubviews() {
    super.layoutSubviews()
    
    var pth: UIBezierPath = UIBezierPath()
    var frameRect: CGRect = .zero
    var pathRect: CGRect = .zero
    
    frameRect = bounds
    
    outerLayer.frame = frameRect
    pathRect.size = frameRect.size
    pth = UIBezierPath(ovalIn: pathRect)
    outerLayer.path = pth.cgPath
    
    frameRect = frameRect.insetBy(dx: outerWidth,dy: outerWidth)
    
    ringLayer.frame = frameRect
    pathRect.size = frameRect.size
    pth = UIBezierPath(ovalIn: pathRect)
    ringLayer.path = pth.cgPath
    
    frameRect = frameRect.insetBy(dx: ringWidth,dy: ringWidth)
    
    bevelGradLayer.frame = frameRect
    pathRect.size = frameRect.size
    pth = UIBezierPath(ovalIn: pathRect)
    bevelLayerMask.path = pth.cgPath
    
    frameRect = frameRect.insetBy(dx: bevelWidth,dy: bevelWidth)
    
    innerGradLayer.frame = frameRect
    pathRect.size = frameRect.size
    pth = UIBezierPath(ovalIn: pathRect)
    innerLayerMask.path = pth.cgPath
    
}

在您发布的代码中,您使用 extension 来尝试对代码功能进行分段... 很有帮助,但它也可能使事情变得更加复杂,并且可以使遵循流程变得有点困难 - 特别是当元素需要相关时。

这是您想要的版本...请注意,它是 @IBDesignable,具有各种用户可用的 @IBInspectable 属性,因此您可以在 IB / Storyboard 中看到它(如果需要) ):

@IBDesignable
class BevelButton: UIButton {
    
    @IBInspectable
    var outerColor: UIColor = UIColor(white: 0.75,alpha: 1.0) {
        didSet {
            outerLayer.fillColor = outerColor.cgColor
        }
    }
    @IBInspectable
    var ringColor: UIColor = .black {
        didSet {
            ringLayer.fillColor = ringColor.cgColor
        }
    }
    @IBInspectable
    var startColor: UIColor = UIColor(white: 0.9,alpha: 1.0) {
        didSet {
            bevelGradLayer.colors = [startColor.cgColor,endColor.cgColor]
            innerGradLayer.colors = [endColor.cgColor,startColor.cgColor]
        }
    }
    @IBInspectable
    var endColor: UIColor = UIColor(white: 0.75,startColor.cgColor]
        }
    }
    
    @IBInspectable
    var outerWidth: CGFloat = 6

    @IBInspectable
    var ringWidth: CGFloat = 2

    @IBInspectable
    var bevelWidth: CGFloat = 6
    
    private let outerLayer = CAShapeLayer()
    private let ringLayer = CAShapeLayer()
    private let innerLayerMask = CAShapeLayer()
    private let innerGradLayer = CAGradientLayer()
    private let bevelLayerMask = CAShapeLayer()
    private let bevelGradLayer = CAGradientLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() -> Void {
        
        layer.addSublayer(outerLayer)
        layer.addSublayer(ringLayer)
        layer.addSublayer(bevelGradLayer)
        layer.addSublayer(innerGradLayer)
        
        bevelGradLayer.mask = bevelLayerMask
        innerGradLayer.mask = innerLayerMask
        
        bevelLayerMask.fillColor = UIColor.black.cgColor
        innerLayerMask.fillColor = UIColor.black.cgColor
        
        outerLayer.fillColor = outerColor.cgColor
        ringLayer.fillColor = ringColor.cgColor
        
        bevelGradLayer.colors = [startColor.cgColor,endColor.cgColor]
        innerGradLayer.colors = [endColor.cgColor,startColor.cgColor]
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        var pth: UIBezierPath = UIBezierPath()
        var frameRect: CGRect = .zero
        var pathRect: CGRect = .zero
        
        frameRect = bounds
        
        outerLayer.frame = frameRect
        pathRect.size = frameRect.size
        pth = UIBezierPath(ovalIn: pathRect)
        outerLayer.path = pth.cgPath
        
        frameRect = frameRect.insetBy(dx: outerWidth,dy: outerWidth)
        
        ringLayer.frame = frameRect
        pathRect.size = frameRect.size
        pth = UIBezierPath(ovalIn: pathRect)
        ringLayer.path = pth.cgPath
        
        frameRect = frameRect.insetBy(dx: ringWidth,dy: ringWidth)
        
        bevelGradLayer.frame = frameRect
        pathRect.size = frameRect.size
        pth = UIBezierPath(ovalIn: pathRect)
        bevelLayerMask.path = pth.cgPath
        
        frameRect = frameRect.insetBy(dx: bevelWidth,dy: bevelWidth)
        
        innerGradLayer.frame = frameRect
        pathRect.size = frameRect.size
        pth = UIBezierPath(ovalIn: pathRect)
        innerLayerMask.path = pth.cgPath
        
    }
    
    override var isHighlighted: Bool {
        get {
            return super.isHighlighted
        }
        set {
            if newValue {
                innerGradLayer.colors = [startColor.cgColor,endColor.cgColor]
                bevelGradLayer.colors = [endColor.cgColor,startColor.cgColor]
            } else {
                bevelGradLayer.colors = [startColor.cgColor,endColor.cgColor]
                innerGradLayer.colors = [endColor.cgColor,startColor.cgColor]
            }
            super.isHighlighted = newValue
        }
    }
}

以下是 Storyboard 中的外观(具有默认属性):

enter image description here

这是 IBInspectable 属性面板:

enter image description here

并且,在运行时,默认状态:

enter image description here

和突出显示状态(垂直翻转渐变):

enter image description here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?