Lottie原理分析(一)

LOTAnimationView

继承自 LOTView ,其本质上还是 UIView

1
2
// 关键字 @compatibility_alias 的意思是给一个类设置一个别名。这样就可以不用重构以前的类,直接使用新的名字替代原有的名字
@compatibility_alias LOTView UIView;

LOTAnimationView 内含有数个构造类方法

1
2
3
4
5
6
7
8
9
10
11
/// Load animation by name from the default bundle, Images are also loaded from the bundle
+ (instancetype)animationNamed:(NSString *)animationName;
+ (instancetype)animationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle;

/// Creates an animation from the deserialized JSON Dictionary
+ (instancetype)animationFromJSON:(NSDictionary *)animationJSON;
+ (instancetype)animationFromJSON:(NSDictionary *)animationJSON inBundle:(NSBundle *)bundle;
/// 源代码第二个方法 animationJSON 参数使用的 nullable 修饰,应该可以使用 nonnull。

/// Loads an animation from a specific file path. WARNING Do not use a web URL for file path.
+ (instancetype)animationWithFilePath:(NSString *)filePath;

这些方法最终调用 LOTComposition 类的对应方法,然后根据返回的 LOTComposition 对象初始化自己

1
2
3
4
+ (instancetype)animationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle {
LOTComposition *comp = [LOTComposition animationNamed:animationName inBundle:bundle];
return [[self alloc] initWithModel:comp inBundle:bundle];
}

LOTAnimationView 在使用 -initWithModel:inBundle: 初始化时做了四件事

1
2
3
4
5
6
7
8
// 赋值 bundle
_bundle = bundle;
// 公共初始化,进行一些参数配置,比如动画速度、进度、是否循环、是否自动意逆画等
[self _commonInit];
// 初始化动画容器,内部只有一个操作 self.clipsToBounds = YES
[self _initializeAnimationContainer];
// 设置场景模型,如果 view 原先已有 model,则会先清空之前的操作,再设置
[self _setupWithSceneModel:model];

_setupWithSceneModel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)_setupWithSceneModel:(LOTComposition *)model {
if (_sceneModel) {
// 如果 view 原先已有 model,先清空之前的操作
[self _removeCurrentAnimationIfNecessary];
[self _callCompletionIfNecessary:NO];
[_compContainer removeFromSuperlayer];
_compContainer = nil;
_sceneModel = nil;
[self _commonInit];
}

_sceneModel = model;
// 初始化 LOTCompositionContainer 对象
_compContainer = [[LOTCompositionContainer alloc] initWithModel:nil inLayerGroup:nil withLayerGroup:_sceneModel.layerGroup withAssestGroup:_sceneModel.assetGroup];
[self.layer addSublayer:_compContainer];
[self _restoreState];
[self setNeedsLayout];
}

LOTComposition

继承自 NSObject,负责 json 数据的解析。即将 json 串解析成 OC 对象,包含 json 的解析的所有模型对象。

1
2
3
4
5
6
7
// 方法内部会先调用单例对象 LOTAnimationCache ,使用 animationName 去掉后.缀后作为 key, 获取已缓存的 LOTComposition 对象,如果获取到没有,才调用构造方法创建。创建完成后,也会加到缓存池中,并赋值 cacheKey 属性。直接使用 init... 方法或 +animationFromJSON: 方法则不会加入到缓存(因为不知道动画的名字)
+(instancetype)animationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle


// 将 json 文件转 NSDictionary 字典对象
NSData *jsonData = [[NSData alloc] initWithContentsOfFile:filePath];
NSDictionary *JSONObject = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error] : nil;

LOTComposition 类中最重要的内部方法为 -_mapFromJSON:withAssetBundle:,用来解析 json 转化的 字典对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary withAssetBundle:(NSBundle *)bundle 
{
// 获取宽高,确定动画 bounds
NSNumber *width = jsonDictionary[@"w"];
NSNumber *height = jsonDictionary[@"h"];
if (width && height) {
CGRect bounds = CGRectMake(0, 0, width.floatValue, height.floatValue);
_compBounds = bounds;
}

// 获取动画起始帧,结束帧,和帧速率
_startFrame = [jsonDictionary[@"ip"] copy];
_endFrame = [jsonDictionary[@"op"] copy];
_framerate = [jsonDictionary[@"fr"] copy];

// 根据动画帧参数算出动画时间
if (_startFrame && _endFrame && _framerate) {
NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1;
NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
_timeDuration = timeDuration;
}

// 获取资源信息,只是建立对应的 _assetJSONMap
NSArray *assetArray = jsonDictionary[@"assets"];
if (assetArray.count) {
_assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
}
// 获取图层信息,需要资源信息 _assetGroup
NSArray *layersJSON = jsonDictionary[@"layers"];
if (layersJSON) {
_layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
withAssetGroup:_assetGroup
withFramerate:_framerate];
}

// 根据 _framerate 获取资源文件建立 _assetMap,之后置空 _assetJSONMap
[_assetGroup finalizeInitializationWithFramerate:_framerate];
}

此方法中又包含两个对象 LOTAssetGroupLOTLayerGroup分别用于存放LOTAssetLOTLayer 对象。二者都是通过 id :对象的方式将子对象存储到字典中,方便之后取用。(LOTAssetid字段,LOTLayerind)。内部还包含其他很多 model,总之,就是对应要解析的 json 字符串需要的各种模型。

###LOTAsset

含有资源的 id、名字、宽、高、文件子路径、包含的子图层等信息。

1
2
3
4
5
6
7
8
NSString *referenceID; 	// 资源id ---id
NSNumber *assetWidth; // 宽度 ---w
NSNumber *assetHeight; // 高度 ---h

NSString *imageName; // 图片名字---p
NSString *imageDirectory; // 文件夹路径 ---u

LOTLayerGroup *layerGroup; // 子图层数组 ---layers

LOTLayer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
NSString *layerName;	// 图层名字 ---nm
NSString *referenceID; // 引用id ---refid
NSNumber *layerID; // 图层id ---ind
LOTLayerType layerType; // 图层类型,有Precomp、Solid、Image、Null、Shape、Unknown 五种枚举 ---ty
NSNumber *parentID; // 起源 ---parent
NSNumber *startFrame; // 开始帧 ---st
NSNumber *inFrame; // ---ip
NSNumber *outFrame; // ---op
NSNumber *timeStretch; // 时间拉伸 ---sr
CGRect layerBounds; // bounds 宽高算出

NSArray<LOTShapeGroup *> *shapes; // ---shapes
NSArray<LOTMask *> *masks; // ---masksProperties

NSNumber *layerWidth; // 图层宽 根据不同的layerType值,有不同的获取方式
NSNumber *layerHeight; // 图层高
UIColor *solidColor;
LOTAsset *imageAsset;

LOTKeyframeGroup *opacity; // 不透明度 -->ks-->o
LOTKeyframeGroup *timeRemapping; // 时间重映射 ---tm
LOTKeyframeGroup *rotation; // 旋转 -->ks-->r或者rz
LOTKeyframeGroup *position; // 位置 -->ks-->p

LOTKeyframeGroup *positionX;
LOTKeyframeGroup *positionY;

LOTKeyframeGroup *anchor; // 角度 -->ks-->a
LOTKeyframeGroup *scale; // 比例 -->ks-->s

LOTMatteType matteType; // 磨砂类型 ---tt

LOTCompositionContainer

顾名思义,LOTCompositionContainer是数据 LOTComposition 展示的控件,继承自 LOTLayerContainer ,进而继承自CALayer 。动画效果全部在这里实现。

LOTAnimationCache

继承自 NSObject,单例对象,用于缓存加载过得 LOTComposition 对象。

内有两个对象animationsCache_lruOrderArray_ ,分别存储缓存的LOTComposition 对象和 有先后顺序的key

1
2
NSMutableDictionary *animationsCache_;
NSMutableArray *lruOrderArray_;

提供的方法有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// Global Cache 获取全局单例对象
+ (instancetype)sharedCache;
/// Adds animation to the cache 添加缓存
- (void)addAnimation:(LOTComposition *)animation forKey:(NSString *)key;
/// Returns animation from cache. 获取缓存的对象
- (LOTComposition * _Nullable)animationForKey:(NSString *)key;
/// Removes a specific animation from the cache 移除一个缓存的对象
- (void)removeAnimationForKey:(NSString *)key;
/// Clears Everything from the Cache 清除所有缓存
- (void)clearCache;
/// Disables Caching Animation Model Objects 不使用缓存池
- (void)disableCaching;

// 其内部还有缓存数量限制,最大为50个,超出上限自动清理。可以学习缓存池的规范写法。