【问题标题】:How do you create a feathered "Circle wipe" animation in iOS?如何在 iOS 中创建羽化的“圆圈擦除”动画?
【发布时间】:2020-12-25 08:35:39
【问题描述】:

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

使用 Core Animation 和遮罩在 iOS 或 Mac OS 中创建“圆圈擦除”动画相当容易。

一种方法是在视图(通常是图像视图)上安装 CAShapeLayer 作为蒙版,并在形状层中安装一个圆圈,并将圆圈从 0 大小设置为足够大以覆盖整个图像 (@987654339 @)

另一种方法是使用径向 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 处停止。

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

效果ok,如果我用窄的模糊带和快速的持续时间运行它,它看起来不错,但并不完美。

这是模糊范围为 0.2,持续时间(我认为)为 0.25 秒时的样子:

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

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

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

通过首先绘制一个圆圈然后对其应用 Core Image 模糊过滤器来创建一个静态模糊圆形(或其他形状)很容易,但是在 iOS 中没有办法对其进行动画处理,CI 模糊过滤器是即使可以,在 iOS 上也太慢了。

这是我在这篇文章中使用我描述的 3 色径向渐变创建动画的项目的链接: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 像素的模糊半径:

【问题讨论】:

  • 好吧,我打算建议为暗角 CIFilter 设置动画。但是后来我偶然发现了你的反对意见,即我在你所做的事情中看不到“不完美”。恐怕读者对目标会是什么样子没有明确的积极想法。
  • 我将尝试找到一个电影圈擦除过渡的示例,以添加到我的问题中,作为我所追求的示例。关于擦除转换的 Wiki 文章(en.wikipedia.org/wiki/Wipe_(transition) 显示了几个我所追求的擦除转换示例,但不包括幸运的圆形擦除。
  • 在模糊百分比设置为 1% 时,动画看起来非常像带有轻微羽化的圆形擦除。随着我增加模糊百分比,它看起来越来越不自然。我刚刚推出了一个新版本,它具有设置模糊百分比、动画持续时间以及可选地在动画的前半部分和后半部分之间添加延迟的控件。随意下载并使用它。
  • 我不确定您在寻找什么,但是带有动画 shadowPath 的 CAShapelayer 怎么样?
  • 这真是个好主意。我得试试。我想要一个 0 阴影偏移来在形状周围创建一个光环。

标签: ios animation caanimation cagradientlayer radial-gradients


【解决方案1】:

您可以在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")

}

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

【讨论】:

  • 这正是我想要的。我想知道未填充的形状是否会产生阴影?如果是这样,那会让你画出模糊的线条和曲线,这是我很长时间以来一直试图弄清楚的。
  • @DuncanC 似乎 shadowPath 将关闭并填充未关闭的路径,因此您不能将其设置为一条线。但是,如果您使用copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:) 复制线条路径,它将创建一个看起来与描边路径相同的填充路径。您将无法为 strokeEnd/Start 设置动画。
  • 酷。我不熟悉那个功能。应该可以通过使用 copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:) 将“线路径”映射到填充路径,将其安装在图层的 shadowPath 中,然后动画更改为路径。
  • 我正在考虑编写自己的函数来将描边路径转换为填充路径。它将涉及在描边路径的任一边界上绘制平行线段。我很确定我有一个可行的方法,但是编写代码会非常复杂和乏味。 `copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:)` 函数使它变得很多更容易。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 2019-07-19
  • 1970-01-01
  • 1970-01-01
  • 2015-12-11
相关资源
最近更新 更多