在UIImageView上添加部分遮罩

3
我想在图像的某个部分上添加0.5的alpha掩码(我将在代码中计算)。 基本上,这是一个5星评级控件,但星星不是单一颜色,而是像这样的一些漂亮的图片:

Star image

这段文字的意思是:图片有一个透明的背景需要保留。所以我想添加一个遮罩或者在评分为3.5时,将图片的一半设置为透明度较低的状态(2个完整的星星和一个只有一半透明度的星星)。但是我不能直接在上面放置一个透明度为0.5的UIView,因为这也会影响到星星显示的背景。你有什么想法吗?

你可以要求设计师也提供给你半星评分的图片。 - undefined
我想要更精确一些,刚才只是举了一个3.5的例子。我希望能够使用例如3.14这样的数值。只要我知道如何绘制透明度,计算就不是问题。问题在于如何实现动态部分遮罩。 - undefined
我猜你需要在星星的边缘周围加上一个遮罩,对吗?这将导致大量的计算,更不用说在缩放时可能会遇到计算问题。在这种情况下,最好自己绘制图像 :) - undefined
是的,看起来我想要的细节水平太复杂了 :) 另外,我尝试过的那些接受自定义星星图片的第三方库根本无法正确渲染它们。 - undefined
@Crocodilu - 你的影子部分是图片的一部分还是在运行时生成的?你能展示一个你希望它看起来的例子吗? - undefined
是的,阴影是图像的一部分。我会尽力添加我想要的结果,虽然我认为这是相当明显的。 - undefined
1个回答

2
您可以使用一个CAGradientLayer作为掩码:
    gLayer.startPoint = CGPoint.zero
    gLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
    gLayer.locations = [
        0.0, 0.5, 0.5, 1.0,
    ]
    gLayer.colors = [
        UIColor.black.cgColor,
        UIColor.black.cgColor,
        UIColor.black.withAlphaComponent(0.5).cgColor,
        UIColor.black.withAlphaComponent(0.5).cgColor,
    ]

这将创建一个水平渐变,左半部分为完全透明,右半部分为50%的透明度。
因此,使用此遮罩的白色视图将如下所示:

enter image description here

如果我们将图像设置为您的明星,它看起来像这样:

enter image description here

如果我们想让星星“填充75%”,我们需要更改位置:
    gLayer.locations = [
        0.0, 0.75, 0.75, 1.0,
    ]

结果为:

enter image description here

这是一个“五星级”评分视图的示例实现:
@IBDesignable
class FiveStarRatingView: UIView {
    
    @IBInspectable
    public var rating: CGFloat = 0.0 {
        didSet {
            var r = rating
            stack.arrangedSubviews.forEach {
                if let v = $0 as? PercentImageView {
                    v.percent = min(1.0, r)
                    r -= 1.0
                }
            }
        }
    }
    
    @IBInspectable
    public var ratingImage: UIImage = UIImage() {
        didSet {
            stack.arrangedSubviews.forEach {
                if let v = $0 as? PercentImageView {
                    v.image = ratingImage
                }
            }
        }
    }
    
    @IBInspectable
    public var tranparency: CGFloat = 0.5 {
        didSet {
            stack.arrangedSubviews.forEach {
                if let v = $0 as? PercentImageView {
                    v.tranparency = tranparency
                }
            }
        }
    }
    
    override var intrinsicContentSize: CGSize {
        return CGSize(width: 100.0, height: 20.0)
    }
    
    private let stack: UIStackView = {
        let v = UIStackView()
        v.axis = .horizontal
        v.alignment = .center
        v.distribution = .fillEqually
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() -> Void {
        addSubview(stack)
        // constrain stack view to all 4 sides
        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: topAnchor),
            stack.leadingAnchor.constraint(equalTo: leadingAnchor),
            stack.trailingAnchor.constraint(equalTo: trailingAnchor),
            stack.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
        // add 5 Percent Image Views to the stack view
        for _ in 1...5 {
            let v = PercentImageView(frame: .zero)
            stack.addArrangedSubview(v)
            v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
        }
    }
    
    private class PercentImageView: UIImageView {
        
        var percent: CGFloat = 0.0 {
            didSet {
                setNeedsLayout()
            }
        }
        
        var tranparency: CGFloat = 0.5 {
            didSet {
                setNeedsLayout()
            }
        }
        
        private let gLayer = CAGradientLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() -> Void {
            gLayer.startPoint = CGPoint.zero
            gLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
            layer.mask = gLayer
        }
        override func layoutSubviews() {
            super.layoutSubviews()

            // we don't want the layer's intrinsic animation
            CATransaction.begin()
            CATransaction.setDisableActions(true)

            gLayer.frame = bounds
            gLayer.locations = [
                0.0, percent as NSNumber, percent as NSNumber, 1.0,
            ]
            gLayer.colors = [
                UIColor.black.cgColor,
                UIColor.black.cgColor,
                UIColor.black.withAlphaComponent(tranparency).cgColor,
                UIColor.black.withAlphaComponent(tranparency).cgColor,
            ]
            
            CATransaction.commit()
        }
    }

}


class StarRatingViewController: UIViewController {

    let ratingView = FiveStarRatingView()
    
    let slider = UISlider()
    let valueLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let starImage = UIImage(named: "star") else {
            fatalError("Could not load image named \"star\"")
        }
        
        // add a slider and a couple labels so we can change the rating
        let minLabel = UILabel()
        let maxLabel = UILabel()
        [slider, valueLabel, minLabel, maxLabel].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
            if let v = $0 as? UILabel {
                v.textAlignment = .center
            }
        }
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            valueLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            valueLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            slider.topAnchor.constraint(equalTo: valueLabel.bottomAnchor, constant: 8.0),
            slider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
            slider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -32.0),
            
            minLabel.topAnchor.constraint(equalTo: slider.bottomAnchor, constant: 8.0),
            minLabel.centerXAnchor.constraint(equalTo: slider.leadingAnchor, constant: 0.0),
            
            maxLabel.topAnchor.constraint(equalTo: slider.bottomAnchor, constant: 8.0),
            maxLabel.centerXAnchor.constraint(equalTo: slider.trailingAnchor, constant: 0.0),
        ])
        minLabel.text = "0"
        maxLabel.text = "5"
        
        ratingView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(ratingView)

        NSLayoutConstraint.activate([
            // constrain the rating view centered in the view
            //  300-pts wide
            //  height will be auto-set by the rating view
            ratingView.topAnchor.constraint(equalTo: minLabel.bottomAnchor, constant: 20.0),
            ratingView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            ratingView.widthAnchor.constraint(equalToConstant: 240.0),
        ])
        
        // use the star image
        ratingView.ratingImage = starImage
        
        // start at rating of 0 stars
        updateValue(0.0)
        slider.value = 0
        
        slider.addTarget(self, action: #selector(self.sliderChanged(_:)), for: .valueChanged)
    }
    
    @objc func sliderChanged(_ sender: UISlider) {
        // round the slider value to 2 decimal places
        updateValue((sender.value * 5.0).rounded(digits: 2))
    }
    
    func updateValue(_ v: Float) -> Void {
        valueLabel.text = String(format: "%.2f", v)
        ratingView.rating = CGFloat(v)
    }
    
}

extension Float {
    func rounded(digits: Int) -> Float {
        let multiplier = Float(pow(10.0, Double(digits)))
        return (self * multiplier).rounded() / multiplier
    }
}

结果:

enter image description here

注意,FiveStarRatingView 类被标记为 @IBDesignable ,因此您可以将其添加到故事板 / IB 中,在设计时设置图像、透明度和评级。

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