Swift 实现聚光灯动效

628 阅读2分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

有时候 UI 上需要突出重点或者添加逐渐显示的效果,看起来就像聚光灯打在了图片上。

gif

我们先来分析一下需要做哪些准备:

  1. 要想显示一个图像的部分内容,可以使用 CALayer 的 mask 属性设置遮罩,让 layer 显示 mask 遮住(非透明)的部分;
  2. 我们需要一个像聚光灯一样的遮罩,透明度从中间向外逐渐变高,即中间的透明度 alpha 为 1,四周的 0。

聚光灯遮罩

首先我们来看怎么绘制聚光灯效果的遮罩。模仿聚光灯的效果需要用到渐变。

渐变分为两种:

  1. 线性渐变:图形以直线的方式,朝着一个方向进行发散,发散后呈现矩形;
  2. 径向渐变:从指定的半径的大小开始,由往外或往内发散,发散后呈现出圆形。

在这里我们就是需要使用径向渐变

/// 聚光灯效果的遮罩
class SpotlightFilterMaskLayer : CALayer {

    /// 坡度的宽度
    var gradientWidth: CGFloat = 40.0

    /// 直径
    var diameter: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }
    
    override func draw(in ctx: CGContext) {
        let origin: CGPoint = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        
        let clearRegionRadius: CGFloat = self.diameter * 0.5
        let blurRegionRadius: CGFloat = clearRegionRadius + gradientWidth

        // 创建颜色空间
        let baseColorSpace = CGColorSpaceCreateDeviceRGB();
        let colours : [CGFloat] = [0.0, 0.0, 0.0, 1,
                                   0.0, 0.0, 0.0, 0.2]
        let colourLocations : [CGFloat] = [0, 1]
        
        // 创建渐变对象
        // - colorSpace: 颜色空间
        // - colorComponents: 颜色分量的强度值数组
        // - locations: 渐变系数数组
        // - count: 设置的渐变系数数组的元素个数
        guard let gradient = CGGradient(colorSpace: baseColorSpace,
                                        colorComponents: colours,
                                        locations: colourLocations,
                                        count: 2) else {
            return
        }

        // 绘制渐变
        // - gradient: 渐变对象
        // - startCenter: 起点
        // - startRadius: 径向半径
        // - endCenter: 终点
        // - endRadius: 径向半径
        // - options: 渐变模式
        // 	- .drawsBeforeStartLocation 表示向里发散
        //  - .drawsAfterEndLocation 表示向外发散
        ctx.drawRadialGradient(gradient,
                               startCenter: origin,
                               startRadius: clearRegionRadius,
                               endCenter: origin,
                               endRadius: blurRegionRadius,
                               options: .drawsBeforeStartLocation);

        ctx.drawPath(using: .fill)
    }
}

设置遮罩

直接通过 layer.mask 属性设置遮罩:

imageView.layer.mask = maskLayer

添加动画

maskLayer 添加一个无限循环的 bounds 变化的动画。

func begin() {
    let fromValue = imageView.bounds
    let animation = CABasicAnimation(keyPath: "bounds")
    animation.fromValue = fromValue
    animation.toValue = CGRect(x: fromValue.origin.x - 100,
                               y: fromValue.origin.y - 100,
                               width: fromValue.size.width + 200,
                               height: fromValue.size.height + 200)

    animation.duration = 1
    animation.autoreverses = true
    animation.repeatCount = MAXFLOAT
    animation.isRemovedOnCompletion = false
    maskLayer.add(animation, forKey: nil)
}

完整的代码

感兴趣可以查看完整的代码:SpotlightAnimation