gov*_*mar 2 cabasicanimation cashapelayer ios uibezierpath swift
我正在尝试实现与下面所示完全相同的动画
我使用 UIBezierPath 和 CABasicAnimation 的输出如下。
这是我的LoaderView代码
class LoaderView: UIView {
private let lineWidth : CGFloat = 5
internal var backgroundMask = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setUpLayers()
createAnimation()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpLayers()
createAnimation()
}
func setUpLayers()
{
backgroundMask.lineWidth = lineWidth
backgroundMask.fillColor = nil
backgroundMask.strokeColor = UIColor.blue.cgColor
layer.mask = backgroundMask
layer.addSublayer(backgroundMask)
}
func createAnimation()
{
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 1
animation.repeatCount = .infinity
backgroundMask.add(animation, forKey: "MyAnimation")
}
override func draw(_ rect: CGRect) {
let sides = 6
let rect = self.bounds
let path = UIBezierPath()
let cornerRadius : CGFloat = 10
let rotationOffset = CGFloat(.pi / 2.0)
let theta: CGFloat = CGFloat(2.0 * .pi) / CGFloat(sides) // How much to turn at every corner
let width = min(rect.size.width, rect.size.height) // Width of the square
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0..<sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
backgroundMask.path = path.cgPath
}}
Run Code Online (Sandbox Code Playgroud)
您需要实现draw(_:)或使用CAAnimation,而不是两者都需要。
作为规则,不要实施draw(_:)为视图类实现。这迫使系统在 CPU 上完成所有渲染,并且不利用 iOS 设备上基于图块的硬件加速渲染。相反,使用 CALayers 和 CAAnimation,让硬件为您完成繁重的工作。
使用 CALayers 和 CAAnimation 你可以得到这样的效果:
我建议执行以下操作:
创建一个完整的圆形六边形形状作为CAShapeLayer. (您的方法中的代码draw()已经生成了一个六边形路径。您可以轻松地调整它以将六边形路径安装到 . 中CAShapeLayer。)
将该形状图层添加为视图的子图层。
创建一个CAGradientLayer以图层中心为起点、以顶部中心为终点的“圆锥曲线”。
将从透明颜色到任何不透明颜色的颜色添加到渐变层,使用一组根据locations需要羽化渐变的颜色。
在六边形图层上安装渐变图层作为蒙版。
创建一个 CABasicAnimation,使渐变层绕 Z 轴每次旋转 1/4 圈。不断运行该动画,直到完成动画。
创建渐变层的代码可能如下所示:
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.type = .conic
gradientLayer.colors = [UIColor.clear.cgColor,
UIColor.clear.cgColor,
UIColor.white.cgColor,
UIColor.white.cgColor]
let center = CGPoint(x: 0.5, y: 0.5)
gradientLayer.locations = [0, 0.3, 0.7, 0.9]
gradientLayer.startPoint = center
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
Run Code Online (Sandbox Code Playgroud)
(如果所属视图的边界发生变化,您将需要更新渐变层的边界。)
旋转渐变层的代码可能如下所示:
private func animateGradientRotationStep() {
let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
animationStepsRemaining -= 1
rotation.fromValue = rotationAngle
rotationAngle += CGFloat.pi / 2
rotation.toValue = rotationAngle
rotation.duration = 0.5
rotation.delegate = self
gradientLayer.add(rotation, forKey: nil)
// After a tiny delay, set the layer's transform to the state at the end of the animation
// so it doesnt jump back once the animation is complete.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
// You have to wrap this step in a CATransaction with setDisableActions(true)
// So you don't get an implicit animation
CATransaction.begin()
CATransaction.setDisableActions(true)
self.gradientLayer.transform = CATransform3DMakeRotation(self.rotationAngle, 0, 0, 1)
CATransaction.commit()
}
}
Run Code Online (Sandbox Code Playgroud)
并且您需要您的视图符合协议CAAnimationDelegate:
extension GradientLayerView: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation,
finished flag: Bool) {
if animating && animationStepsRemaining > 0 {
animateGradientRotation()
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,图层的变换属性是“隐式动画”的,这意味着默认情况下系统会生成变化的动画。我们可以利用这一事实,对隐式动画进行一些调整。这使得动画功能更简单:
// This version of the function takes advantage of the fact
// that a layer's transform property is implicitly animated
private func animateGradientRotationStep() {
animationStepsRemaining -= 1
rotationAngle += CGFloat.pi / 2
// MARK: - CATransaction begin
// Use a CATransaction to set the animation duration, timing function, and completion block
CATransaction.begin()
CATransaction.setAnimationDuration(0.5)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear))
CATransaction.setCompletionBlock {
self.animationDidStop(finished:true)
}
self.gradientLayer.transform = CATransform3DMakeRotation(self.rotationAngle, 0, 0, 1)
CATransaction.commit()
// MARK: CATransaction end -
}
Run Code Online (Sandbox Code Playgroud)
该版本需要稍微不同的完成函数,因为它不使用 CAAnimation:
func animationDidStop(finished flag: Bool) {
delegate?.animationStepComplete(animationStepsRemaining)
if animating && animationStepsRemaining > 0 {
animateGradientRotationStep()
}
Run Code Online (Sandbox Code Playgroud)
我制作了一个创建此类动画的小示例应用程序。
您可以通过此链接从 Github 下载演示应用程序。
我不确定如何复制示例动画的一部分是六边形的颜色一开始似乎是亮白色,然后过渡到黄色。我的示例应用程序创建了一个动画,其中六边形是固定颜色并从不透明过渡到透明。
这是该项目的自述文件:
该项目演示了如何使用“圆锥”渐变来遮罩视图并创建圆形动画。
它使用CAGradientLayerof 类型.conic,设置为大部分不透明,后半部分过渡为透明。它将渐变层作为蒙版安装在包含黄色六边形的形状层上。
Gadient 层如下所示:
(在灰色棋盘背景下以蓝色渲染,以便您可以看到从不透明到透明的过渡。)
渐变的不透明(蓝色)部分使形状图层可见。渐变的透明部分隐藏(遮罩)形状图层的那些部分,渐变图层的部分透明部分使形状图层的那些部分部分透明。
动画只是围绕图层中心在 Z 轴上旋转渐变图层。它一次将图层旋转 1/4 圈,每次动画步骤完成时,它都会创建一个新动画,将蒙版再旋转 1/4 圈。
当你掩盖六边形形状时,有点难以理解发生了什么。我创建了一个变体,其中添加了图像视图作为自定义视图的子视图。其动画如下所示:
该应用程序的窗口如下所示: