CABasicAnimation 在非圆形上模拟 'pulse' 效果动画
CABasicAnimation to emulate a 'pulse' effect animation on a non-circle shape
我正在使用 CBasicAnimation 在按钮上创建脉动效果。
效果脉冲出 UIView 的形状,只有边框。
虽然动画工作正常,但我使用 CABasicAnimation(keyPath: "transform.scale")
没有得到想要的效果。
我正在使用一个包含 3 个动画的动画组:borderWidth、transform.scale 和 opacity。
class Pulsing: CALayer {
var animationGroup = CAAnimationGroup()
var initialPulseScale:Float = 1
var nextPulseAfter:TimeInterval = 0
var animationDuration:TimeInterval = 1.5
var numberOfPulses:Float = Float.infinity
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init (numberOfPulses:Float = Float.infinity, position:CGPoint, pulseFromView:UIView, rounded: CGFloat) {
super.init()
self.borderColor = UIColor.black.cgColor
self.contentsScale = UIScreen.main.scale
self.opacity = 1
self.numberOfPulses = numberOfPulses
self.position = position
self.bounds = CGRect(x: 0, y: 0, width: pulseFromView.frame.width, height: pulseFromView.frame.height)
self.cornerRadius = rounded
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.setupAnimationGroup(view: pulseFromView)
DispatchQueue.main.async {
self.add(self.animationGroup, forKey: "pulse")
}
}
}
func borderWidthAnimation() -> CABasicAnimation {
let widthAnimation = CABasicAnimation(keyPath: "borderWidth")
widthAnimation.fromValue = 2
widthAnimation.toValue = 0.5
widthAnimation.duration = animationDuration
return widthAnimation
}
func createScaleAnimation (view:UIView) -> CABasicAnimation {
let scale = CABasicAnimation(keyPath: "transform.scale")
DispatchQueue.main.async {
scale.fromValue = view.layer.value(forKeyPath: "transform.scale")
}
scale.toValue = NSNumber(value: 1.1)
scale.duration = 1.0
scale.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
return scale
}
func createOpacityAnimation() -> CABasicAnimation {
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.duration = animationDuration
opacityAnimation.fromValue = 1
opacityAnimation.toValue = 0
opacityAnimation.fillMode = .removed
return opacityAnimation
}
func setupAnimationGroup(view:UIView) {
self.animationGroup = CAAnimationGroup()
self.animationGroup.duration = animationDuration + nextPulseAfter
self.animationGroup.repeatCount = numberOfPulses
self.animationGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
self.animationGroup.animations = [createScaleAnimation(view: view), borderWidthAnimation(), createOpacityAnimation()]
}
}
class ViewController: UIViewController {
@IBOutlet weak var pulsingView: UIView!
let roundd:CGFloat = 20
override func viewDidLoad() {
super.viewDidLoad()
pulsingView.layer.cornerRadius = roundd
let pulse = Pulsing(
numberOfPulses: .greatestFiniteMagnitude,
position: CGPoint(x: pulsingView.frame.width/2,
y: pulsingView.frame.height/2)
, pulseFromView: pulsingView, rounded: roundd)
pulse.zPosition = -10
self.pulsingView.layer.insertSublayer(pulse, at: 0)
}
}
我的问题是 transform.scale 是保持 UIView 的宽高比,它在动画期间从中跳动。
如何使脉冲变大,使高度和宽度上的间距均匀?见截图。
按相同比例缩放宽度和高度会导致边缘间距不等。您需要将图层的宽度和高度增加相同的值。这是一个加法运算,而不是乘法。现在,为了实现这种脉动效果,您需要为图层的边界设置动画。
如果您希望边缘之间的间距是动态的,请选择一个比例因子并将其应用于单个维度。无论您选择宽度还是高度都没有关系,只要它只应用于一个。假设您选择宽度增加 1.1 倍。计算目标宽度,然后计算增量。
let scaleFactor: CGFloat = 1.1
let targetWidth = view.bounds.size.width * scaleFactor
let delta = targetWidth - view.bounds.size.width
获得增量后,将其应用于图层在 x 和 y 维度上的边界。利用 insetBy(dx:)
方法计算结果矩形。
let targetBounds = self.bounds.insetBy(dx: -delta / 2, dy: -delta / 2)
为清楚起见,我已将您的 createScaleAnimation(view:)
方法重命名为 createExpansionAnimation(view:)
。将它们结合在一起我们有:
func createExpansionAnimation(view: UIView) -> CABasicAnimation {
let anim = CABasicAnimation(keyPath: "bounds")
DispatchQueue.main.async {
let scaleFactor: CGFloat = 1.1
let targetWidth = view.bounds.size.width * scaleFactor
let delta = targetWidth - view.bounds.size.width
let targetBounds = self.bounds.insetBy(dx: -delta / 2, dy: -delta / 2)
anim.duration = 1.0
anim.fromValue = NSValue(cgRect: self.bounds)
anim.toValue = NSValue(cgRect: targetBounds)
}
return anim
}
我正在使用 CBasicAnimation 在按钮上创建脉动效果。 效果脉冲出 UIView 的形状,只有边框。
虽然动画工作正常,但我使用 CABasicAnimation(keyPath: "transform.scale")
没有得到想要的效果。
我正在使用一个包含 3 个动画的动画组:borderWidth、transform.scale 和 opacity。
class Pulsing: CALayer {
var animationGroup = CAAnimationGroup()
var initialPulseScale:Float = 1
var nextPulseAfter:TimeInterval = 0
var animationDuration:TimeInterval = 1.5
var numberOfPulses:Float = Float.infinity
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init (numberOfPulses:Float = Float.infinity, position:CGPoint, pulseFromView:UIView, rounded: CGFloat) {
super.init()
self.borderColor = UIColor.black.cgColor
self.contentsScale = UIScreen.main.scale
self.opacity = 1
self.numberOfPulses = numberOfPulses
self.position = position
self.bounds = CGRect(x: 0, y: 0, width: pulseFromView.frame.width, height: pulseFromView.frame.height)
self.cornerRadius = rounded
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.setupAnimationGroup(view: pulseFromView)
DispatchQueue.main.async {
self.add(self.animationGroup, forKey: "pulse")
}
}
}
func borderWidthAnimation() -> CABasicAnimation {
let widthAnimation = CABasicAnimation(keyPath: "borderWidth")
widthAnimation.fromValue = 2
widthAnimation.toValue = 0.5
widthAnimation.duration = animationDuration
return widthAnimation
}
func createScaleAnimation (view:UIView) -> CABasicAnimation {
let scale = CABasicAnimation(keyPath: "transform.scale")
DispatchQueue.main.async {
scale.fromValue = view.layer.value(forKeyPath: "transform.scale")
}
scale.toValue = NSNumber(value: 1.1)
scale.duration = 1.0
scale.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
return scale
}
func createOpacityAnimation() -> CABasicAnimation {
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.duration = animationDuration
opacityAnimation.fromValue = 1
opacityAnimation.toValue = 0
opacityAnimation.fillMode = .removed
return opacityAnimation
}
func setupAnimationGroup(view:UIView) {
self.animationGroup = CAAnimationGroup()
self.animationGroup.duration = animationDuration + nextPulseAfter
self.animationGroup.repeatCount = numberOfPulses
self.animationGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
self.animationGroup.animations = [createScaleAnimation(view: view), borderWidthAnimation(), createOpacityAnimation()]
}
}
class ViewController: UIViewController {
@IBOutlet weak var pulsingView: UIView!
let roundd:CGFloat = 20
override func viewDidLoad() {
super.viewDidLoad()
pulsingView.layer.cornerRadius = roundd
let pulse = Pulsing(
numberOfPulses: .greatestFiniteMagnitude,
position: CGPoint(x: pulsingView.frame.width/2,
y: pulsingView.frame.height/2)
, pulseFromView: pulsingView, rounded: roundd)
pulse.zPosition = -10
self.pulsingView.layer.insertSublayer(pulse, at: 0)
}
}
我的问题是 transform.scale 是保持 UIView 的宽高比,它在动画期间从中跳动。
如何使脉冲变大,使高度和宽度上的间距均匀?见截图。
按相同比例缩放宽度和高度会导致边缘间距不等。您需要将图层的宽度和高度增加相同的值。这是一个加法运算,而不是乘法。现在,为了实现这种脉动效果,您需要为图层的边界设置动画。
如果您希望边缘之间的间距是动态的,请选择一个比例因子并将其应用于单个维度。无论您选择宽度还是高度都没有关系,只要它只应用于一个。假设您选择宽度增加 1.1 倍。计算目标宽度,然后计算增量。
let scaleFactor: CGFloat = 1.1
let targetWidth = view.bounds.size.width * scaleFactor
let delta = targetWidth - view.bounds.size.width
获得增量后,将其应用于图层在 x 和 y 维度上的边界。利用 insetBy(dx:)
方法计算结果矩形。
let targetBounds = self.bounds.insetBy(dx: -delta / 2, dy: -delta / 2)
为清楚起见,我已将您的 createScaleAnimation(view:)
方法重命名为 createExpansionAnimation(view:)
。将它们结合在一起我们有:
func createExpansionAnimation(view: UIView) -> CABasicAnimation {
let anim = CABasicAnimation(keyPath: "bounds")
DispatchQueue.main.async {
let scaleFactor: CGFloat = 1.1
let targetWidth = view.bounds.size.width * scaleFactor
let delta = targetWidth - view.bounds.size.width
let targetBounds = self.bounds.insetBy(dx: -delta / 2, dy: -delta / 2)
anim.duration = 1.0
anim.fromValue = NSValue(cgRect: self.bounds)
anim.toValue = NSValue(cgRect: targetBounds)
}
return anim
}