Lottie-OC
pod 'lottie-ios', '2.5.0'
LOTAnimation
通过_mapFromJson
来反序列化
例如:
NSArray *assetArray = jsonDictionary[@"assets"];
_assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
NSArray *layersJSON = jsonDictionary[@"layers"];
_layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
withAssetGroup:_assetGroup
withFramerate:_framerate]
LOTLayerGroup
即为LOTLayer的集合
内部存在_layers
数组即为所有的LOTLayer
LOTLayer
即为对具体的Layer信息的解析,其layer的主要来源也是包括LottieJson文件的最外层的Layer信息,以及其Asset中图片对应的Layer信息
在对Layer信息进行反序列化解析后,根据不同的Layer进行区分类型,
LOTLayerTypePrecomp
LOTLayerTypeImage
LOTLayerTypeSolid
例如针对LOTLayerTypePrecomp
和LOTLayerTypeImage
就要进行解析操作 查找其中的asset和layer
if (_layerType == LOTLayerTypePrecomp) {
[assetGroup buildAssetNamed:_referenceID withFramerate:framerate];
} else if (_layerType == LOTLayerTypeImage) {
[assetGroup buildAssetNamed:_referenceID withFramerate:framerate];
_imageAsset = [assetGroup assetModelForID:_referenceID];
} else if (_layerType == LOTLayerTypeSolid) {
_solidColor = [UIColor LOT_colorWithHexString:solidColor];
}
//maskLayer
LOTMask *mask = [[LOTMask alloc] initWithJSON:maskJSON];
//shapeLayer
id shapeItem = [LOTShapeGroup shapeItemWithJSON:shapeJSON];
LOTAssetGroup
在LotAsswtGroup类中,存储了是所有的图片资源,imageLayer
就也是通过这里找到图片信息
其内部存在字典_assetMap
为图片id为Key和其对应的 资源信息
- (void)setRootDirectory:(NSString *)rootDirectory {
_rootDirectory = rootDirectory;
[_assetMap enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, LOTAsset * _Nonnull obj, BOOL * _Nonnull stop) {
obj.rootDirectory = rootDirectory;
}];
}
- (void)buildAssetNamed:(NSString *)refID
withFramerate:(NSNumber * _Nonnull)framerate {
if ([self assetModelForID:refID]) {
return;
}
NSDictionary *assetDictionary = _assetJSONMap[refID];
if (assetDictionary) {
LOTAsset *asset = [[LOTAsset alloc] initWithJSON:assetDictionary
withAssetGroup:self
withAssetBundle:_assetBundle
withFramerate:framerate];
_assetMap[refID] = asset;
}
}
LOTAsset
即为具体的图片资源信息,其内部将lottie的json文件assets
中的信息,包括图片的宽高以及其包括的layer、图片名字或者图片路径等
// lottie json文件中指定的信息
@property (nonatomic, readonly, nullable) NSString *referenceID;
@property (nonatomic, readonly, nullable) NSNumber *assetWidth;
@property (nonatomic, readonly, nullable) NSNumber *assetHeight;
@property (nonatomic, readonly, nullable) NSString *imageName;
@property (nonatomic, readonly, nullable) NSString *imageDirectory;
@property (nonatomic, readonly, nullable) LOTLayerGroup *layerGroup;
// 通过指定其所在AssetGroup来指定图片文件的查找路径
@property (nonatomic, readwrite) NSString *rootDirectory;
@property (nonatomic, readonly) NSBundle *assetBundle;
加载
bundle中加载
+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle { LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:bundle];
}文件中加载
+ (nullable instancetype)animationWithFilePath:(nonnull NSString *)filePath { LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:[NSBundle mainBundle]];
laScene.rootDirectory = [filePath stringByDeletingLastPathComponent];
}
- (void)setRootDirectory:(NSString *)rootDirectory {
_rootDirectory = rootDirectory;
self.assetGroup.rootDirectory = rootDirectory;
}d
Animation Cache
[LOTAnimationCache sharedCache]
也是一个单例 内部使用一个字典,以名字为key,Composition为Value缓存。并使用LRU数组 进行存储数量和增加删除
LOTAnimationView
初始化
// 指定动画初始化
- (instancetype)initWithModel:(LOTComposition *)model inBundle:(NSBundle *)bundle {
}
// 网络请求 下载json文件 初始化动画
- (instancetype)initWithContentsOfURL:(NSURL *)url {
}
在初始化方法中 最重要的是初始化了其属性LOTCompositionContainer
_compContainer = [[LOTCompositionContainer alloc] initWithModel:nil inLayerGroup:nil withLayerGroup:_sceneModel.layerGroup withAssestGroup:_sceneModel.assetGroup];
[self.layer addSublayer:_compContainer];
而LOTCompositionContainer
才是执行一切动画的核心
LOTCompositionContainer
继承自LOTLayerContainer
->CALayer
其初始化过程,就是找到json文件中涉及的所有子layer,以及子layer的子layer等,构建其正确的layer层级关系
而其继承的LOTLayerContainer
中主要作用即为 根据其layer的类型 构建其layer内容
例如:
//LOTLayerContainer
if (layer.layerType == LOTLayerTypeImage) {
[self _setImageForAsset:layer.imageAsset];
}
if (layer.layerType == LOTLayerTypeShape &&
layer.shapes.count) {
[self buildContents:layer.shapes];
}
if (layer.layerType == LOTLayerTypeSolid) {
_wrapperLayer.backgroundColor = layer.solidColor.CGColor;
}
if (layer.masks.count) {
_maskLayer = [[LOTMaskContainer alloc] initWithMasks:layer.masks];
_wrapperLayer.mask = _maskLayer;
}
其查找图片Layer的规则为
先容asset的rootPath中查找 找不到再从bundle中查找
- (void)_setImageForAsset:(LOTAsset *)asset {
if (asset.rootDirectory.length > 0) {
rootDirectory = [asset.rootDirectory stringByAppendingPathComponent:asset.imageDirectory];
}
NSString *imagePath = [rootDirectory stringByAppendingPathComponent:asset.imageName];
id<LOTImageCache> imageCache = [LOTCacheProvider imageCache];
if (imageCache) {
image = [imageCache imageForKey:imagePath];
if (!image) {
image = [UIImage imageWithContentsOfFile:imagePath];
[imageCache setImage:image forKey:imagePath];
}
} else {
image = [UIImage imageWithContentsOfFile:imagePath];
}
} else {
NSString *imagePath = [asset.assetBundle pathForResource:asset.imageName ofType:nil];
image = [UIImage imageWithContentsOfFile:imagePath];
}
_wrapperLayer.contents = (__bridge id _Nullable)(image.CGImage);
}
我们可以通过设置查找image的Rootpath 然后设置其cache中的image 来指定我们想要替换的图片资源
播放
NSTimeInterval duration = (ABS(toEndFrame.floatValue - fromStartFrame.floatValue) / _sceneModel.framerate.floatValue);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"currentFrame"];
animation.speed = _animationSpeed;
animation.fromValue = fromStartFrame;
animation.toValue = toEndFrame;
animation.duration = duration;
animation.fillMode = kCAFillModeBoth;
animation.repeatCount = _loopAnimation ? HUGE_VALF : 1;
animation.autoreverses = _autoReverseAnimation;
animation.delegate = self;
animation.removedOnCompletion = NO;
if (offset != 0) {
animation.beginTime = CACurrentMediaTime() - (offset * 1 / _animationSpeed);
}
[_compContainer addAnimation:animation forKey:kCompContainerAnimationKey];
而在LOTLayerContainer
中 通过重写CALayer
的代理 实现通过currentFrame
属性来做动画
- (void)display {
@synchronized(self) {
LOTLayerContainer *presentation = self;
if (self.animationKeys.count &&
self.presentationLayer) {
presentation = (LOTLayerContainer *)self.presentationLayer;
}
[self displayWithFrame:presentation.currentFrame];
}
}
然后根据当前的frame帧 来更细layer的形态即可
Animation KeyPath
// log所有的关键路径
- (void)logHierarchyKeypaths;
//设置关键路径值
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegates
forKeypath:(LOTKeypath * _Nonnull)keypath;
//获取关键路径的值
- (nullable NSArray *)keysForKeyPath:(nonnull LOTKeypath *)keypath;
Add SubView
将自定义的View添加到AnimationView中 并将该layer添加到该层级keypath的layer中 进行展示
//AnimationPath
- (void)addSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath {
LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
[wrapperView addSubview:view];
[self addSubview:wrapperView];
[_compContainer addSublayer:wrapperView.layer toKeypathLayer:keypath];
}