现在项目是用SVGA做动画,现在运用的是SVGAPlayer这个库。由于该库很久没有更新,有些需求很难去满意一些场景,因此根据源码上做了部分优化。
Demo下载:SVGAPlayer_OptimizedDemo (Readme还没更新,等代码注释写好后再同步)
- 核心代码:
SVGARePlayer(.h .m)
、SVGAVideoEntity+Extension(.h .m)
- 依赖:SVGAPlayer(根据
SVGAPlayer
重构的) - 加强版:
SVGAExPlayer.swift
,详细介绍在【iOS】SVGAParsePlayer – 快捷SVGA播映器,文章还没更新,等代码注释写好后再同步。
起因:最近有个需求,需求动态修正某个动画的难道区间,也就是一时要播整个动画,一时只播某个范围。虽然说SVGAPlayer
有startAnimationWithRange:reverse:
这个办法能够操控播映区域,可是我觉得运用起来比较费事,如下:
class ViewController: UIViewController {
let player = SVGAPlayer()
override func viewDidLoad() {
super.viewDidLoad()
player.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
player.loops = 1
player.clearsAfterStop = false
player.delegate = self
view.addSubview(player)
SVGAParser().parse(withNamed: "animation_file", in: nil) { [weak self] videoItem in
guard let self else { return }
self.player.videoItem = videoItem
self.player.startAnimation()
}
}
}
extension ViewController: SVGAPlayerDelegate {
func svgaPlayerDidFinishedAnimation(_ player: SVGAPlayer!) {
if xxx {
// 某些条件下只播映 90~120帧
self.player.startAnimation(with: NSRange(location: 90, length: 120), reverse: false)
} else {
// 某些条件下要完好播映 0~120帧
self.player.startAnimation(with: NSRange(location: 0, length: 120), reverse: false)
}
}
}
看上去代码不多,首先loops要设置为1,其次clearsAfterStop要设置为false,最后监听回调来切换,这些都得看源码和调试多次才干够做到无缝切换播映区域。我仍是觉得比较费事的,主要是不能以比较快捷的方式去动态修正ta的播映区域。
而且看了源码后,我感觉内部逻辑比较乱,感觉有优化的空间,由于项目多处运用,为了提升一下日后的扩展性,决议根据SVGAPlayer
重构一个新的SVGA播映器。
SVGARePlayer
SVGARePlayer
就是根据SVGAPlayer
重构一个新的SVGA播映器。起初是彻底拷贝了SVGAPlayer
的代码,然后在此基础上进行重构,相同也是用Objective-C写的,外部接口根本跟SVGAPlayer
坚持一致,而内部则是根本按我的风格进行修整、删减、加强、封装后的一个全新的播映器,这是为了能便利逐渐替换项目中本来的SVGAPlayer
才这么设计。
优化
详细优化了什么呢?其实也没优化啥,主要有两个:
一、同步了烘托进程,避免重复构建
在本来的代码中有这样的写法:
- (void)setVideoItem:(SVGAVideoEntity *)videoItem {
...省掉...
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self clear];
[self draw];
}];
}
- (void)startAnimation {
...省掉...
if (self.videoItem == nil) {
...省掉...
} else if (self.drawLayer == nil) {
self.videoItem = _videoItem;
}
...省掉...
}
当我们运用时,一般都会这么干:
player.videoItem = videoItem;
[player startAnimation];
这样在第一次播映时,大概率会重复调用了两次draw
办法(由于-setVideoItem:
办法中运用了异步),假如新翻开的页面有多个SVGA而且都是比较复杂的动画,重复的draw
或许会对GPU和CPU有比较显着的担负(卡顿)。
因此我对此加了线程保护,确保不会重复draw
:
#import <pthread.h>
static inline void _jp_dispatch_sync_on_main_queue(void (^block)(void)) {
if (pthread_main_np()) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
- (void)setVideoItem:(SVGAVideoEntity *)videoItem {
...省掉...
_jp_dispatch_sync_on_main_queue(^{
[self clear];
[self draw];
});
}
从作者的写法上应该是为了在子线程中也能设置这个videoItem
,尽量坚持本来的逻辑,同步到主线程进行绘制。
二、避免CADisplayLink的内存走漏
运用CADisplayLink
一般都会运用NSProxy
然后转发给self
去执行对应办法,这是为了避免循环引用。作者没有用不知道是不是为了性能的问题,为了安全我仍是加上了,另外运用preferredFramesPerSecond
替换了frameInterval
的设置:
- (void)__addLink {
[self __removeLink];
self.displayLink = [CADisplayLink displayLinkWithTarget:[_JPProxy proxyWithTarget:self] selector:@selector(__linkHandle)];
self.displayLink.preferredFramesPerSecond = self.videoItem.FPS;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.mainRunLoopMode];
}
当时项目没有发生内存走漏那是由于源码中重写了-willMoveToSuperview:
办法,在这儿判断了父视图为空就移除定时器,不过这种是建立在播映器必须有被放到父视图上才干释放,假如有人没有把播映器add
到父视图上就播映了动画,那就会造成内存走漏了。
优化最主要的也就这两点,当然SVGARePlayer
可不仅只做了这些小优化,最主要是扩展了新的功用。
扩展
一、静音
SVGA动画可是有音频的,但SVGAPlayer居然不供给静音的功用,这儿我补上了:
/// 是否静音
@property (nonatomic, assign) BOOL isMute;
详细实现是将其内部的音频播映器的音量设置成0,现在能够随时翻开/封闭静音了。
二、回转播映
这是本来就有的功用,不过只能经过-startAnimationWithRange:reverse:
办法去设置,不是不可,只是调用该办法每次都会重置loopCount
,假如设置了loops
,那么调用这办法(还有-startAnimation
也相同)都会重置次数的统计,对于某些需求计数的当地就不太友爱。
对此我内部进行了从头设计,改用特点去进行回转:
/// 是否回转播映
@property (nonatomic, assign) BOOL isReversing;
运用起来便利多了,而且不会重置完结次数(我另外供给了-resetLoopCount
办法专门去重置)。
三、动态播映区间
这个也是本来的功用,相同只能经过-startAnimationWithRange:reverse:
办法去设置,源码内部运用currentRange
来办理的(不知道为啥不揭露),仅仅经过这办法去设置我觉得太费事了,而且是经过NSRange
设置开始帧和长度,不好把控。
我改成了运用准确的帧数(下标)开始帧数 startFrame 和 完毕帧数 endFrame 来设置播映区间,为了避免越界的情况呈现,我内部做了防护,而且外部不同单一设置这两个值,得经过我的办法去一起设置:
#pragma mark 替换SVGA资源+设置播映区间
- (void)setVideoItem:(nullable SVGAVideoEntity *)videoItem
currentFrame:(NSInteger)currentFrame;
- (void)setVideoItem:(nullable SVGAVideoEntity *)videoItem
startFrame:(NSInteger)startFrame
endFrame:(NSInteger)endFrame;
- (void)setVideoItem:(nullable SVGAVideoEntity *)videoItem
startFrame:(NSInteger)startFrame
endFrame:(NSInteger)endFrame
currentFrame:(NSInteger)currentFrame;
#pragma mark 设置播映区间
/// 重置开始帧数为最小帧数(0),完毕帧数为最大帧数(videoItem.frames)
- (void)resetStartFrameAndEndFrame;
/// 设置开始帧数,完毕帧数为最大帧数(videoItem.frames)
- (void)setStartFrameUntilTheEnd:(NSInteger)startFrame;
/// 设置完毕帧数,开始帧数为最小帧数(0)
- (void)setEndFrameFromBeginning:(NSInteger)endFrame;
- (void)setStartFrame:(NSInteger)startFrame endFrame:(NSInteger)endFrame;
- (void)setStartFrame:(NSInteger)startFrame endFrame:(NSInteger)endFrame currentFrame:(NSInteger)currentFrame;
经过以上办法即可修正播映区域,可在动画进程中修正:
此处需求阐明一下,startFrame
和endFrame
是绝对帧数值,而且startFrame <= endFrame
。
也就是说,正常播映时是startFrame ~> endFrame
的进程,而回转播映(isReversing
为YES)则是endFrame ~> startFrame
的进程。
为了不被搞混,供给了这两个计算特点便利运用:
/// 头部帧数 = isReversing ? endFrame : startFrame
@property (readonly) NSInteger leadingFrame;
/// 尾部帧数 = isReversing ? startFrame : endFrame
@property (readonly) NSInteger trailingFrame;
四、其他
最主要的是以上三点,其他的就是一些比较琐碎的,例如供给了中止播映后的场景选择:
typedef NS_ENUM(NSUInteger, SVGARePlayerStoppedScene) {
/// 中止后清空图层
SVGARePlayerStoppedScene_ClearLayers = 0,
/// 中止后留在最后
SVGARePlayerStoppedScene_StepToTrailing = 1,
/// 中止后回到最初
SVGARePlayerStoppedScene_StepToLeading = 2,
};
/// 设置特点操控:主动调用`stopAnimation`后的情形
@property (nonatomic, assign) SVGARePlayerStoppedScene userStoppedScene;
/// 设置特点操控:完结一切播映后(需求设置`loops > 0`)的情形
@property (nonatomic, assign) SVGARePlayerStoppedScene finishedAllScene;
/// 也能够调用办法自由操控
- (void)stopAnimation:(SVGARePlayerStoppedScene)scene;
另外还有一些比较常用的特点揭露拜访,例如:
/// 当时帧数
@property (nonatomic, assign, readonly) NSInteger currentFrame;
/// 当时进度
@property (readonly) float progress;
/// 当时播映次数
@property (nonatomic, assign, readonly) NSInteger loopCount;
以上这些都是新增的特性,剩下的例如署理办法的回调和资料替换等办法我都有保留,而且有所优化,用法跟SVGAPlayer根本坚持一致,这儿就不多介绍了。
One more thing…
SVGAExPlayer
SVGAExPlayer是继承于SVGARePlayer
,是ta的加强版,用Swift写的,除了原有功用外,还有以下新特性:
✅ 内置SVGA解析器;
✅ 带有播映状态且可操控;
✅ 可自定义下载器;
✅ 避免重复加载;
✅ 兼容 OC & Swift;
✅ API超级简略易用。
简略介绍一下,本来SVGAPlayer
的运用方式:
let player = SVGAPlayer()
override func viewDidLoad() {
super.viewDidLoad()
...UI初始化...
// 1.创立 SVGA 动画解析器
let parser = SVGAParser()
// 2.加载 SVGA 动画文件
parser.parse(withNamed: "animation_name", in: nil) { [weak self] videoItem in
guard let self, videoItem else { return }
// 3.将 SVGA 动画加载到播映器中
self.player.videoItem = videoItem
// 4.开始播映动画
self.player.startAnimation()
}
}
用SVGAExPlayer
只需求:
player.play("animation_name")
// 内部现已做好了重复加载的防护,只需名称相同,就不会有重复加载的问题,非常适合在可复用的滚动列表中运用。
player.play("animation_name")
player.play("animation_name")
player.play("animation_name")
player.play("animation_name")
player.play("animation_name")
......
其实这个是我之前写的【iOS】SVGAParsePlayer – 快捷SVGA播映器,之前是继承于SVGAPlayer
,现在换成了SVGARePlayer
而且彻底适配了,变得愈加强壮。
- PS:由于最近工作比较忙,所以文章还没更新,等之后把注释都写好后再同步到文章和Github的Readme,现在这儿只能先偷个懒。
Demo
-
SVGAPlayer_OptimizedDemo (Readme还没更新,等代码注释写好后再同步)
- 核心代码:
SVGARePlayer(.h .m)
、SVGAVideoEntity+Extension(.h .m)
- 依赖:SVGAPlayer(根据
SVGAPlayer
重构的) - 加强版:
SVGAExPlayer.swift
,详细介绍在【iOS】SVGAParsePlayer – 快捷SVGA播映器,文章还没更新,等代码注释写好后再同步。
- 核心代码:
-
- 一个用于快速预览
Lottie
&SVGA
的Mac小工具。 - 运用了
SVGAExPlayer
。
- 一个用于快速预览