前言
在接到一个做点赞喷射动画的需求时,我们通常会想到用CAEmitterLayer也就是粒子动画去做。但是CAEmitterLayer是无法满足以下需求的:
- 每次点击屏幕的喷射都从起点开始
- 连续喷射时不覆盖之前喷射的东西
- 不同样式的2个图片一个一个喷射而不是2个同时喷射
因此需要一个自定义的layer来实现这样的功能。
演示
演示实现
实现的方式由两种:
- 封装成对UIImageView的动画管理类
- 继承CALayer实现自定义Layer
本文用的是第二种,因为第一种对性能的影响比第二种大,喷射出的UIImageView最终需要removeFromSuperview,这样的操作是很损失性能的,尤其是这个点赞适用于直播这样的页面中。第二种动画在iPhone 8的实测中可以保持60帧。
确定需要做动画的layer
我的实现是写一个代理获取需要喷射动画的View
func displayViewForEmitter() -> UIView
通过代理的setter方法获取显示的View
weak var yz_delegate: YZEmitterLayerDelegate? {
willSet {
if newValue?.displayViewForEmitter() != nil {
displayView = newValue?.displayViewForEmitter()
displayView?.layer.addSublayer(self)
}
}
}
实现动画
通过CABasicAnimation和CAKeyframeAnimation实现位移、旋转、透明度动画。其中需要给position动画设置CAAnimationDelegate的代理,方便在动画结束时移除layer
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
removeFromSuperlayer()
}
使用
import UIKit
import YZEmitterLayer
class ViewController: UIViewController, YZEmitterLayerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer.init(target: self, action: #selector(begin))
view.addGestureRecognizer(tap)
}
func displayViewForEmitter() -> UIView {
return view
}
// 自定义path
// func emitterPath() -> UIBezierPath {
// let path = UIBezierPath.init()
// path.move(to: CGPoint.init(x: 100, y: 100))
// path.addLine(to: CGPoint.init(x: 300, y: 300))
// return path
// }
@objc func begin() {
let animationLayer = YZEmitterLayer.emitterLayer(size: CGSize.init(width: 32, height: 32), center: view.center, image: UIImage.init(named: "love")!)
animationLayer.yz_delegate = self
animationLayer.fromAlpha = 1.0
animationLayer.toAlpha = 0
animationLayer.fromScale = 0
animationLayer.toScale = 1
animationLayer.roateRange = Double.pi / 4
animationLayer.startAnimation()
}
}
我将这个封装成了pod,欢迎使用YZEmitterLayer
网友评论