【问题标题】:CABasicAnimation to emulate a 'pulse' effect animation on a non-circle shapeCABasicAnimation 在非圆形上模拟“脉冲”效果动画
【发布时间】:2020-05-01 03:24:40
【问题描述】:

我正在使用 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 在动画期间脉动的纵横比。

我怎样才能使脉冲增长,以便在高度和宽度上都有均匀的间距?见截图。

【问题讨论】:

  • 也请分享你的路径....
  • 路径只是一个具有固定宽度/高度和圆角的 UIView。圆角甚至不是必需的。方形 UIView 应该产生相同的结果。
  • 您也可以发布该代码吗?我需要运行它来结束
  • @jawadAli - 现在所有内容都已发布。
  • 你是 scalig 脉冲,它是 CALayer ......但不是视图......它是白色的

标签: swift animation core-animation cabasicanimation


【解决方案1】:

以相同的因子缩放宽度和高度将导致边缘周围的间距不相等。您需要将图层的宽度和高度增加相同的值。这是加法运算,而不是乘法运算。现在,对于这种脉动效果,您需要为图层的边界设置动画。

如果您希望边缘之间的间距是动态的,则选择一个比例因子并将其应用于单个维度。选择宽度还是高度无关紧要,只要它只应用于一个即可。假设您选择将宽度增加 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
}

【讨论】:

  • 很好的答案。您的解释是有道理的,您的解决方案非常完美。 (必须再等 11 个小时才能奖励您的赏金)。感谢您的帮助!
  • @Joe 我的意思是要提到这一点,但您确实不需要使用 DispatchQueues。如果您将所有内容都保留在主线程中,您的代码会大大简化。
  • 看来我需要它。没有它,它会崩溃:主线程检查器:在后台线程上调用 UI API:-[UIView bounds] 在这一行:让 targetWidth = view.bounds.size.width * scaleFactor
  • @Joe 它崩溃的原因是因为这条线:DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async。如果你删除了它和其他 DispatchQueue 调用,你应该很高兴。
猜你喜欢
  • 2021-12-03
  • 2015-03-20
  • 2011-12-26
  • 1970-01-01
  • 2014-01-20
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 1970-01-01
相关资源
最近更新 更多