如何在 iOS 中创建羽化 "Circle wipe" 动画?

How do you create a feathered "Circle wipe" animation in iOS?

我的问题需要很多解释。如果您想跳到问题,请在末尾查找粗体“问题:”。

使用核心动画和蒙版在 iOS 或 Mac OS 中创建“圆形擦除”动画相当容易。

一种方法是在视图(通常是图像视图)上安装一个 CAShapeLayer 作为遮罩,并在形状层中安装一个圆,并将圆从 0 大小动画化到足以覆盖整个图像( r = sqrt(height^2 + width^2)

另一种方法是使用径向 CAGradientLayer 作为蒙版,将渐变的中心设置为不透明的颜色,并在图像的角落之外突然过渡以清除。然后为渐变层的“位置”值设置动画,将清晰的颜色移动到图像的中心。

这两种方法都创建了一个锐边的“圆擦除”,导致图像缩小到一个点并消失。

效果是这样的:

经典的电影圆擦除动画具有羽化边缘,而不是尖锐的边框。圆圈的外缘是完全透明的。当您向中心移动时,图像在相当短的距离内变得越来越坚固,然后图像从那里到中心完全不透明。随着圆圈的缩小,从透明到不透明过渡的厚度保持不变,圆圈发臭到一点并消失。 (蒙版是一个圆形,中间不透明,边缘略微模糊,圆的半径缩小为0,而模糊边缘的厚度保持不变。)

这是一个旧学校圈擦拭的例子。这在技术上是一个“Iris Out”,其中过渡到黑色,但想法是相同的:

https://youtu.be/IqDhAW3TDR8?t=90

我不知道如何使用形状图层作为蒙版来实现带有羽化边缘的圆形擦除。形状图层始终是锋利的边缘。我可以通过使用一个形状图层来获得相当接近的效果,其中圆圈被 50% 的不透明度描边,并且圆圈的中间填充了 100% 不透明度的颜色,但这不会实现从透明到不透明的平滑过渡我在追。

我尝试过的另一种方法是使用包含 3 种颜色的径向渐变。我将内部颜色设置为不透明,将外部颜色设置为透明。我将外部颜色的位置设置为 > 1(比如 1.2),将中间位置设置为 1,将内部位置设置为 0。位置数组包含 [1.2, 1.0, 0] 这使得遮罩的整个部分覆盖了图像不透明,有一个羽化过渡到清晰,发生在图像的边缘之外。

然后我分两步制作位置数组的动画。 在第一步中,我将位置数组设置为 [0, 0, 0.2]。这会导致羽化带从角落移入并停止在距图像中心 0.2 处。

在第 2 步中,我将位置数组从 [0, 0, 0.2] 动画化到 [0,0,0]。这会导致内部透明到不透明的中心缩小到一个点并消失。

效果还可以,如果我运行它有一个窄的模糊带和一个快速的持续时间,它看起来不错,但并不完美。

这是模糊范围为 0.2、持续时间(我认为)为 0.25 秒的情况:

但是,如果我在动画步骤之间引入暂停,您会看到效果中的缺陷:

问题:有没有一种方法可以使用 Core Animation 将具有小模糊半径(5 点左右)的圆从全尺寸视图缩小到 0?

同样的问题也适用于其他类型的划像动画:钥匙孔、星形、侧划、方框等等。对于那些,我用于羽化圆形擦除的径向渐变技巧根本不起作用。 (您使用 CAShapeLayer 创建那些专门的擦除动画,这是您想要动画的形状,然后将其大小从大调整到小。

创建静态模糊圆(或其他形状)很容易,方法是先画一个圆,然后对其应用 Core Image 模糊滤镜,但无法在 iOS 中对其进行动画处理。即使可以,CI 模糊滤镜在 iOS 上也太慢了。

这是我在这个 post 中使用我描述的 3 色径向渐变创建动画的项目 link:https://github.com/DuncanMC/GradientView.git.

编辑:

根据 James 的评论,我尝试使用带有阴影的形状图层作为遮罩。效果是正确的,但是从图像的不透明部分到被阴影层遮盖的大部分透明部分仍然存在明显的过渡。即使阴影不透明度为 1.0,阴影仍然大部分是透明的。这是它的样子:

使圆形的线条颜色部分透明会有所帮助,并在图像的不透明部分和大部分透明的阴影之间提供过渡。这是上图,但以 2 点的线宽和 0.6 的不透明度抚摸形状图层:

编辑 #2:已解决!

James 使用 shadowPath 的想法是解决方案。除了 shadowPath 之外,您不需要在遮罩层中添加任何东西。如果这样做,您可以使用 shadowRadius 属性 来控制模糊量,并在一个步骤中平滑地将阴影路径从完全包围视图的圆圈向下移动到消失点。

我已经在 https://github.com/DuncanMC/GradientView.git 更新了我的 github 项目,以包含径向渐变动画和 shadowPath 动画以供比较。

这是完成的效果,首先是 5 像素的模糊半径:

然后使用 30 像素的模糊半径:

您可以在 CAShapeLayer 上使用 shadowPath 创建一个轮廓模糊的圆作为遮罩。

创建一个没有填充或描边的形状图层(我们只想看到阴影)并将其作为图层蒙版添加到视图中。

let shapeLayer = CAShapeLayer()
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: 0, height: 0)
shapeLayer.shadowOpacity = 1
shapeLayer.shadowRadius = 5
self.maskLayer = shapeLayer
self.layer.mask = shapeLayer

并使用 CABasicAnimation 对其进行动画处理。

func show(_ show: Bool) {
    
    let center = CGPoint(x: self.bounds.width/2, y: self.bounds.height/2)
    var newPath: CGPath
    if show {
        let radius = sqrt(self.bounds.height*self.bounds.height + self.bounds.width*self.bounds.width)/2 //assumes view is square
        newPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true).cgPath
    } else {
        newPath = UIBezierPath(arcCenter: center, radius: 0.01, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true).cgPath
    }
    
    let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
    shadowAnimation.fromValue = self.maskLayer.shadowPath
    shadowAnimation.toValue = newPath
    shadowAnimation.duration = totalDuration

    self.maskLayer.shadowPath = newPath
    self.maskLayer.add(shadowAnimation, forKey: "shadowAnimation")

}

因为您可以为阴影设置任何路径,它应该也适用于其他形状(星形、方块等)。