问题描述
我正在尝试重新创建Apple在其活动应用中使用的活动环。这是那些不知道的图片。
我在重建它方面做得不错,我特别努力的是重叠的阴影。最后,我使用的解决方法是将戒指分成两部分,前75%和后25%,这样我就可以使后25%带有阴影并看起来与自身重叠。
现在,我已经完成了此操作,动画计时变得更加困难。现在,我需要处理三个动画。
- 戒指的前75%
- 戒指的最后25%
- 如果环超过100%,则旋转环
以下是演示此视频的视频。 Streamable Link.
出于说明目的,以下是最后25%的颜色,因此您可以visualise it.
如您所见,动画的时间安排有点混乱。所以我的时间安排如下
- 如果戒指的填充率为75%或以下,则需要2.25秒的时间来填充缓解时间功能
- 如果戒指的填充量介于75%和100%之间,则前75%的填充时间为1秒,而后25%的填充时间为1.25秒,具有缓动计时功能。
- 如果环的填充率超过100%,则前75%会花费1秒,后25%会花费1秒,并且环的旋转也需要1秒,并具有缓动计时功能。
我的问题是,是否可以链接这些单独的CABasicAnimations,以便我可以将总时间设置为2.25秒,并为该组设置计时功能,以便为每个动画动态计算计时,并且计时功能会影响所有三个?
percent =填充戒指的数量
-
gradientMaskPart1 =第一个75%的环形层
-
gradientMaskPart2 =最后25%的环形层
-
containerLayer =图层,可同时容纳两个渐变蒙版部件并旋转以模拟重叠的环。
private func animateRing() { let needsMultipleAnimations = percent <= 0.75 ? false : true CATransaction.begin() if needsMultipleAnimations { CATransaction.setCompletionBlock(ringEndAnimation) } let basicAnimation = CABasicAnimation(keyPath: "strokeEnd") basicAnimation.fromValue = currentFill basicAnimation.tovalue = percent > 0.75 ? 0.75 : percent currentFill = Double(percent) basicAnimation.fillMode = .forwards basicAnimation.isRemovedOnCompletion = false basicAnimation.duration = needsMultipleAnimations ? 1 : 2.25 basicAnimation.timingFunction = needsMultipleAnimations ? .none : camediatimingFunction(name: camediatimingFunctionName.eaSEOut) gradientMaskPart1.add(basicAnimation,forKey: "basicAnimation") CATransaction.commit() }
-
private func ringEndAnimation() {
let needsMultipleAnimations = percent <= 1 ? false : true
CATransaction.begin()
if needsMultipleAnimations { CATransaction.setCompletionBlock(rotateRingAnimation) }
let duration = needsMultipleAnimations ? 1 : 1.25
let timingFunction: camediatimingFunction? =
needsMultipleAnimations ? .none : camediatimingFunction(name: camediatimingFunctionName.eaSEOut)
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.fromValue = 0
basicAnimation.tovalue = percent <= 1 ? (percent-0.75)*4 : 1
basicAnimation.duration = duration
basicAnimation.fillMode = .forwards
basicAnimation.isRemovedOnCompletion = false
basicAnimation.timingFunction = timingFunction
self.gradientMaskPart2.isHidden = false
self.gradientMaskPart2.add(basicAnimation2,forKey: "basicAnimation")
CATransaction.commit()
}
-
private func rotateRingAnimation() {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 2*CGFloat.pi
rotationAnimation.tovalue = 2*CGFloat.pi+((2*(percent-1)*CGFloat.pi))
rotationAnimation.duration = 1
rotationAnimation.fillMode = camediatimingFillMode.forwards
rotationAnimation.isRemovedOnCompletion = false
rotationAnimation.timingFunction = camediatimingFunction(name: camediatimingFunctionName.eaSEOut)
self.containerLayer.add(rotationAnimation,forKey: "rotation")
}
解决方法
此解决方案演示了如何使用CABasicAnimation
与专用的CAAnimationGroup
同步制作多层动画。
- 为应该同步设置动画的图层创建一个容器
CALayer
子类。我们称之为TestLayer
- 为需要设置动画的每个图层定义属性。
- 当动画通过
needsDisplay(forKey:)
修改特定图层时,在TestLayer
中覆盖display
以接收keyPath
调用。 - 在
init(layer:)
中覆盖TestLayer
,将子层分配给testLayer.presentation()
层,该层呈现TestLayer
的动画。
这是托管TestView
的{{1}}的全部代码。
TestLayer
-
class TestLayer: CALayer {
@NSManaged @objc dynamic var layer1: CAGradientLayer
@NSManaged @objc dynamic var layer2: CAGradientLayer
override init() {
super.init()
let gradientLayer1 = CAGradientLayer()
let gradientLayer2 = CAGradientLayer()
gradientLayer1.colors = [UIColor.red.cgColor,UIColor.green.cgColor]
gradientLayer2.colors = [UIColor.green.cgColor,UIColor.red.cgColor]
gradientLayer1.startPoint = CGPoint(x: 0.5,y: 0.5)
gradientLayer1.endPoint = CGPoint(x: 1.0,y: 0.5)
gradientLayer2.startPoint = CGPoint(x: 0,y: 0.5)
gradientLayer2.endPoint = CGPoint(x: 0.5,y: 0.5)
addSublayer(gradientLayer1)
addSublayer(gradientLayer2)
layer1 = gradientLayer1
layer2 = gradientLayer2
}
override init(layer: Any) {
super.init(layer: layer)
if let testLayer = layer as? TestLayer {
layer1 = testLayer.layer1
layer2 = testLayer.layer2
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSublayers() {
var bounds1 = self.bounds
bounds1.size.height /= 2.0
var bounds2 = bounds1
bounds2.origin.y = bounds1.maxY
super.layoutSublayers()
layer1.frame = bounds1
layer2.frame = bounds2
}
override class func needsDisplay(forKey key: String) -> Bool {
if key == "layer1" || key == "layer2" {
return true
}
return super.needsDisplay(forKey: key)
}
}
如果要测试它,请创建一个虚拟视图控制器,并像这样覆盖其class TestView: UIView {
override class var layerClass: AnyClass {
get {
return TestLayer.self
}
}
var testLayer: TestLayer {
get {
return layer as! TestLayer
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startAnimation() {
let animLayer1 = CABasicAnimation(keyPath: "layer1.startPoint")
animLayer1.fromValue = CGPoint(x: 0.5,y: 0.5)
animLayer1.toValue = CGPoint(x: 0.0,y: 0.5)
animLayer1.duration = 1.0
animLayer1.autoreverses = true
let animLayer2 = CABasicAnimation(keyPath: "layer2.endPoint")
animLayer2.fromValue = CGPoint(x: 0.5,y: 0.5)
animLayer2.toValue = CGPoint(x: 1.0,y: 0.5)
animLayer2.duration = 1.0
animLayer2.autoreverses = true
let group = CAAnimationGroup()
group.duration = 1.0
group.autoreverses = true
group.repeatCount = Float.greatestFiniteMagnitude
group.animations = [animLayer1,animLayer2]
testLayer.add(group,forKey: "TestAnim")
}
}
:
viewWillAppear