Lottie 動(dòng)畫(huà)簡(jiǎn)介
- Lottie 動(dòng)畫(huà)是 airbnb 最新開(kāi)源的動(dòng)畫(huà)解決方案,支持多種平臺(tái),可以使我們不費(fèi)吹灰之力就可以將動(dòng)畫(huà)接入 app 中,只需要一個(gè) json 文件即可。再也不需要進(jìn)行復(fù)雜的動(dòng)畫(huà)繪制工作了,節(jié)約了很多時(shí)間。下面我來(lái)詳細(xì)介紹一下 Lottie 在 iOS 端的接入過(guò)程以及部分源碼解讀。
-
部分效果如下:luanchpage.gif
接入過(guò)程
首先由 CocoaPods 接入該庫(kù)
$ pod search Lottie $ pod 'lottie-ios', '~> 2.5.0' 然后創(chuàng)建 pod 文件 $ pod init,然后將 lottie-ios 添加到 Podfile 中 最后執(zhí)行 $ pod install 就完成了 Lottie 庫(kù)的接入
導(dǎo)出 json 文件
我們可以在 ae 中下載插件,將 aep 文件導(dǎo)出為 json 形式,具體如何操作請(qǐng)看 Lottie開(kāi)源動(dòng)畫(huà)庫(kù)介紹
-
導(dǎo)出的 json 文件格式化后類(lèi)型如下:json文件格式.png
- 其中的部分參數(shù)定義為:
- v :版本號(hào)
- ip:原大小
- op:目標(biāo)大小
- w:寬度
- h:高度
- nm:文件名稱(chēng)
- assets:圖片文件
- fonts:字體
- layers:動(dòng)畫(huà)效果
- markers:
- chars:文字效果
- 其中的部分參數(shù)定義為:
將 json 和它所依賴(lài)的圖片導(dǎo)入自定義 bundle 中
- 這里有一個(gè)問(wèn)題:當(dāng)我們?cè)谧鰡?dòng)導(dǎo)航圖時(shí),有時(shí)可能會(huì)需要使用到多個(gè) page,需要多個(gè) json 文件,比如像開(kāi)頭的動(dòng)畫(huà)那樣,需要三個(gè) json 文件。而 UI 導(dǎo)出的圖片基本上名字都差不多,這時(shí)候如果我們不加分辨,直接導(dǎo)入三個(gè) json 文件,會(huì)使得圖片出現(xiàn)錯(cuò)亂。因?yàn)槿齻€(gè) json 文件中的圖片名基本完全相同,這時(shí)候我們就需要
- 打開(kāi) json 文件中的 assets 屬性,修改其中的圖片名。
- 或者修改圖片的上層目錄,使得三個(gè) json 文件所指向的文件為三個(gè)不同的目錄。
使用 LOTAnimationView 加載 json 文件
- 這里使用了懶加載的方式來(lái)加載這個(gè) view, 具體過(guò)程如下,首先需要找到自定義 bundle 的地址,然后再在 bundle 中找到 json 文件并且加載。

播放動(dòng)畫(huà)
-
在 Lottie 的 API 中我們可以看到,只需要簡(jiǎn)單的
[firstAnimationView play]即可播放動(dòng)畫(huà)。到這里簡(jiǎn)單的需求基本完成,有時(shí)我們還需要在動(dòng)畫(huà)上添加文字和按鈕來(lái)讓這個(gè) page 能夠響應(yīng)一些事件,這時(shí)候我們就需要先將這個(gè)LOTAniamtionView放到視圖層級(jí)的最底層,使用sendSubviewToBack:這個(gè)方法即可簡(jiǎn)單做到。然后再添加一個(gè) backgroundView 作為容器來(lái)盛放 Label,Button 等,當(dāng)要求文字與按鈕也和動(dòng)畫(huà)一樣有漸入效果時(shí),我們就可以在滑動(dòng)到這個(gè) page 時(shí)做一個(gè)簡(jiǎn)單的動(dòng)畫(huà),使用下面這個(gè)方法即可做到-(void)animatedForPageOne { [self.firstAnimationView playToProgress:1 withCompletion:nil]; [UIView animateWithDuration:1.5 animations:^{ self.p1bottomView.alpha = 1; }]; } 當(dāng)用戶(hù)如果滑動(dòng)到第三個(gè) page 后并沒(méi)有進(jìn)入應(yīng)用,而是滑動(dòng)返回到第二個(gè) page 時(shí),例如文章開(kāi)始時(shí)給出的動(dòng)圖,這時(shí)我們就需要當(dāng)動(dòng)畫(huà)播放完后,再返回動(dòng)畫(huà)的第一幀,否則就會(huì)有一個(gè)跳幀的效果,非常不好看,具體的方法也很簡(jiǎn)單,只需要在監(jiān)聽(tīng)到 ScrollView 滾動(dòng)翻頁(yè)時(shí)設(shè)置
self.firstAnimationView.animationProgress = 0;即可將上一個(gè) page 的動(dòng)畫(huà)調(diào)到第一幀,當(dāng)執(zhí)行返回時(shí)即可從第一幀開(kāi)始重新播放。
其他效果
- 當(dāng)我們?cè)?
LOTAniamtionView上添加其他控件時(shí),會(huì)發(fā)現(xiàn)有時(shí)候會(huì)出現(xiàn)與動(dòng)畫(huà)不兼容的現(xiàn)象,比如遮擋等,這時(shí)候我們可以選擇修改控件的位置或者直接將動(dòng)畫(huà)的某一部分刪除,這個(gè)過(guò)程也非常簡(jiǎn)單,我們只需要找到 json 文件,找到相應(yīng)的控件,把它刪除即可。
部分源碼分析
Lottie是如何解析json文件的?
-
- 從 LOTAniamtionView 的源文件中找到了答案,首先我們可以看到在它的頭文件中找到很多初始化方法
@interface LOTAnimationView : LOTView // 默認(rèn)從 main bundle 中加載 json 文件和圖片 + (nonnull instancetype)animationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(init(name:)); // 從給定的 bundle 中加載 json 文件和圖片,這個(gè) animationName 參數(shù)實(shí)際上就是 json 文件的文件名 + (nonnull instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle NS_SWIFT_NAME(init(name:bundle:)); // 直接從給定的 json 文件中加載動(dòng)畫(huà),默認(rèn)從 mainBundle 中加載 + (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:)); // 從一個(gè)文件 url 中加載動(dòng)畫(huà),但是不能夠使用 web url 來(lái)作為參數(shù) + (nonnull instancetype)animationWithFilePath:(nonnull NSString *)filePath NS_SWIFT_NAME(init(filePath:)); // 給定一個(gè)反序列化的 json 文件和一個(gè)特定的 bundle 名來(lái)初始化動(dòng)畫(huà) + (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(init(json:bundle:)); /// 直接使用 LOTCompostion 來(lái)創(chuàng)建動(dòng)畫(huà)效果 - (nonnull instancetype)initWithModel:(nullable LOTComposition *)model inBundle:(nullable NSBundle *)bundle; /// 異步的從指定的 url 中加載動(dòng)畫(huà),這個(gè) url 為 webUrl - (nonnull instancetype)initWithContentsOfURL:(nonnull NSURL *)url;至于最后是如何初始化的,我們可以根據(jù)初始化方法在
LOTComposition類(lèi)中找到??這個(gè)方法,這個(gè)方法用來(lái)解析 json 文件- (void)_mapFromJSON:(NSDictionary *)jsonDictionary withAssetBundle:(NSBundle *)bundle { 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]; // 初始 frame _endFrame = [jsonDictionary[@"op"] copy]; // 動(dòng)畫(huà)結(jié)束 frame _framerate = [jsonDictionary[@"fr"] copy]; // 動(dòng)畫(huà)變化率 // 根據(jù)動(dòng)畫(huà)的這三個(gè)變量來(lái)判斷動(dòng)畫(huà)需要執(zhí)行的時(shí)間 if (_startFrame && _endFrame && _framerate) { NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1; NSTimeInterval timeDuration = frameDuration / _framerate.floatValue; _timeDuration = timeDuration; } // 圖片文件數(shù)組 NSArray *assetArray = jsonDictionary[@"assets"]; if (assetArray.count) { _assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate]; } // 動(dòng)畫(huà)layer層數(shù)組 NSArray *layersJSON = jsonDictionary[@"layers"]; if (layersJSON) { _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON withAssetGroup:_assetGroup withFramerate:_framerate]; } [_assetGroup finalizeInitializationWithFramerate:_framerate]; }在這個(gè)方法中,我們注意到了它調(diào)用了圖片數(shù)組的初始化方法和 layer 層數(shù)組的初始化方法,具體來(lái)看一下:
// LOTAssetGroup 初始化方法到最后我們?cè)?LOTAsset 中找到了這個(gè)解析 json 數(shù)據(jù)的方法 - (void)_mapFromJSON:(NSDictionary *)jsonDictionary withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup withFramerate:(NSNumber *)framerate { _referenceID = [jsonDictionary[@"id"] copy]; // 指定圖片的 referenceID,這個(gè) id 是 json 文件自動(dòng)為每張圖片編號(hào)的,表示他的唯一標(biāo)識(shí)符 if (jsonDictionary[@"w"]) { // 圖片寬度 _assetWidth = [jsonDictionary[@"w"] copy]; } if (jsonDictionary[@"h"]) { // 圖片高度 _assetHeight = [jsonDictionary[@"h"] copy]; } if (jsonDictionary[@"u"]) { // 圖片的路徑,這個(gè)路徑表示儲(chǔ)存圖片的文件夾 _imageDirectory = [jsonDictionary[@"u"] copy]; } if (jsonDictionary[@"p"]) { // p 表示圖片的真實(shí)id,而不是 referenceID _imageName = [jsonDictionary[@"p"] copy]; } NSArray *layersJSON = jsonDictionary[@"layers"]; // 對(duì)圖片 layer 的配置,具體來(lái)看一下 if (layersJSON) { _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON withAssetGroup:assetGroup withFramerate:framerate]; }LOTALayerGroup 文件中對(duì) json 文件的解析
- (void)_mapFromJSON:(NSArray *)layersJSON withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup withFramerate:(NSNumber *)framerate { NSMutableArray *layers = [NSMutableArray array]; NSMutableDictionary *modelMap = [NSMutableDictionary dictionary]; NSMutableDictionary *referenceMap = [NSMutableDictionary dictionary]; for (NSDictionary *layerJSON in layersJSON) { LOTLayer *layer = [[LOTLayer alloc] initWithJSON:layerJSON withAssetGroup:assetGroup withFramerate:framerate]; [layers addObject:layer]; modelMap[layer.layerID] = layer; if (layer.referenceID) { referenceMap[layer.referenceID] = layer; } } _referenceIDMap = referenceMap; _modelMap = modelMap; _layers = layers; } // 在這個(gè)方法內(nèi)部更深層的調(diào)用了 LOTALayer 的初始化方法 - (void)_mapFromJSON:(NSDictionary *)jsonDictionary withAssetGroup:(LOTAssetGroup *)assetGroup withFramerate:(NSNumber *)framerate { _layerName = [jsonDictionary[@"nm"] copy]; // layer 的名字 _layerID = [jsonDictionary[@"ind"] copy]; // layer 的 id,表示是第幾個(gè) layer NSNumber *layerType = jsonDictionary[@"ty"]; // 表示 layer 的類(lèi)型,這個(gè)變量是一個(gè)枚舉類(lèi)型 // typedef enum : NSInteger { // LOTLayerTypePrecomp, // LOTLayerTypeSolid, // LOTLayerTypeImage, // LOTLayerTypeNull, // LOTLayerTypeShape, // LOTLayerTypeUnknown // } LOTLayerType; _layerType = layerType.integerValue; if (jsonDictionary[@"refId"]) { // 這里的 refId 和圖片文件的 referenceID 指向的是同一個(gè)標(biāo)識(shí)符,表示這個(gè) layer 動(dòng)畫(huà)會(huì)作用在 referenceID 指向的圖片上 _referenceID = [jsonDictionary[@"refId"] copy]; } _parentID = [jsonDictionary[@"parent"] copy]; // 父layer if (jsonDictionary[@"st"]) { _startFrame = [jsonDictionary[@"st"] copy]; // 開(kāi)始的 frame } _inFrame = [jsonDictionary[@"ip"] copy]; // 開(kāi)始的 frame,通常和 startFrame 值相同 _outFrame = [jsonDictionary[@"op"] copy]; // 最后一幀的 frame if (jsonDictionary[@"sr"]) { // 縮放 _timeStretch = [jsonDictionary[@"sr"] copy]; } else { _timeStretch = @1; } // 判斷 layer 是哪種類(lèi)型,并且做相應(yīng)的處理 if (_layerType == LOTLayerTypePrecomp) { _layerHeight = [jsonDictionary[@"h"] copy]; // 高度 _layerWidth = [jsonDictionary[@"w"] copy]; // 寬度 [assetGroup buildAssetNamed:_referenceID withFramerate:framerate]; } else if (_layerType == LOTLayerTypeImage) { [assetGroup buildAssetNamed:_referenceID withFramerate:framerate]; _imageAsset = [assetGroup assetModelForID:_referenceID]; _layerWidth = [_imageAsset.assetWidth copy]; _layerHeight = [_imageAsset.assetHeight copy]; } else if (_layerType == LOTLayerTypeSolid) { _layerWidth = jsonDictionary[@"sw"]; _layerHeight = jsonDictionary[@"sh"]; NSString *solidColor = jsonDictionary[@"sc"]; _solidColor = [UIColor LOT_colorWithHexString:solidColor]; } _layerBounds = CGRectMake(0, 0, _layerWidth.floatValue, _layerHeight.floatValue); NSDictionary *ks = jsonDictionary[@"ks"]; NSDictionary *opacity = ks[@"o"]; // 不透明度 if (opacity) { _opacity = [[LOTKeyframeGroup alloc] initWithData:opacity]; [_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) { return LOT_RemapValue(inValue, 0, 100, 0, 1); }]; } NSDictionary *timeRemap = jsonDictionary[@"tm"]; if (timeRemap) { _timeRemapping = [[LOTKeyframeGroup alloc] initWithData:timeRemap]; [_timeRemapping remapKeyframesWithBlock:^CGFloat(CGFloat inValue) { return inValue * framerate.doubleValue; }]; } NSDictionary *rotation = ks[@"r"]; // 旋轉(zhuǎn) if (rotation == nil) { rotation = ks[@"rz"]; } if (rotation) { _rotation = [[LOTKeyframeGroup alloc] initWithData:rotation]; [_rotation remapKeyframesWithBlock:^CGFloat(CGFloat inValue) { return LOT_DegreesToRadians(inValue); }]; } NSDictionary *position = ks[@"p"]; // 位置 if ([position[@"s"] boolValue]) { // Separate dimensions _positionX = [[LOTKeyframeGroup alloc] initWithData:position[@"x"]]; _positionY = [[LOTKeyframeGroup alloc] initWithData:position[@"y"]]; } else { _position = [[LOTKeyframeGroup alloc] initWithData:position ]; } NSDictionary *anchor = ks[@"a"]; //錨點(diǎn) if (anchor) { _anchor = [[LOTKeyframeGroup alloc] initWithData:anchor]; } NSDictionary *scale = ks[@"s"]; // 縮放比例 if (scale) { _scale = [[LOTKeyframeGroup alloc] initWithData:scale]; [_scale remapKeyframesWithBlock:^CGFloat(CGFloat inValue) { return LOT_RemapValue(inValue, -100, 100, -1, 1); }]; } _matteType = [jsonDictionary[@"tt"] integerValue]; NSMutableArray *masks = [NSMutableArray array]; for (NSDictionary *maskJSON in jsonDictionary[@"masksProperties"]) { LOTMask *mask = [[LOTMask alloc] initWithJSON:maskJSON]; [masks addObject:mask]; } _masks = masks.count ? masks : nil; NSMutableArray *shapes = [NSMutableArray array]; for (NSDictionary *shapeJSON in jsonDictionary[@"shapes"]) { // 尺寸 id shapeItem = [LOTShapeGroup shapeItemWithJSON:shapeJSON]; if (shapeItem) { [shapes addObject:shapeItem]; } } _shapes = shapes; // 額外效果 NSArray *effects = jsonDictionary[@"ef"]; if (effects.count > 0) { NSDictionary *effectNames = @{ @0: @"slider", @1: @"angle", @2: @"color", @3: @"point", @4: @"checkbox", @5: @"group", @6: @"noValue", @7: @"dropDown", @9: @"customValue", @10: @"layerIndex", @20: @"tint", @21: @"fill" }; for (NSDictionary *effect in effects) { NSNumber *typeNumber = effect[@"ty"]; NSString *name = effect[@"nm"]; NSString *internalName = effect[@"mn"]; NSString *typeString = effectNames[typeNumber]; if (typeString) { NSLog(@"%s: Warning: %@ effect not supported: %@ / %@", __PRETTY_FUNCTION__, typeString, internalName, name); } } } }由上可以看到,在 LOTComposition , LOTLayer, LOTAssets 這幾個(gè)類(lèi)中完成了 json 數(shù)據(jù)的解析。
LOTAniamtionView 的使用方法
-
首先我們可以通過(guò)上述方法初始化一個(gè) animationView, 然后繼續(xù)觀察 API 中的屬性和方法:
/// 判斷是否正在播放動(dòng)畫(huà) @property (nonatomic, readonly) BOOL isAnimationPlaying; /// 判斷動(dòng)畫(huà)是否需要循環(huán)播放 @property (nonatomic, assign) BOOL loopAnimation; /// 如果這個(gè)屬性值和 loopAnimation 都為 YES, 那么動(dòng)畫(huà)會(huì)先倒序播放,然后再正序播放 @property (nonatomic, assign) BOOL autoReverseAnimation; // 設(shè)置 progress 的值 從 0 ~ 1,表示這個(gè)動(dòng)畫(huà)執(zhí)行的程度,當(dāng)設(shè)定為 1 時(shí)表示動(dòng)畫(huà)結(jié)束 @property (nonatomic, assign) CGFloat animationProgress; // 為動(dòng)畫(huà)設(shè)置一個(gè)播放速度,如果想倒序播放,則可以把這個(gè)值設(shè)定為負(fù)數(shù) @property (nonatomic, assign) CGFloat animationSpeed; // 只讀屬性,獲取的值為當(dāng) speed = 1 時(shí)動(dòng)畫(huà)的播放秒數(shù) @property (nonatomic, readonly) CGFloat animationDuration; // 是否緩存動(dòng)畫(huà), 默認(rèn)為 YES, 對(duì)于只需要播放一次的動(dòng)畫(huà),比如啟動(dòng)頁(yè)動(dòng)畫(huà),可以設(shè)定為 NO @property (nonatomic, assign) BOOL cacheEnable; /// 當(dāng)動(dòng)畫(huà)播放完畢所調(diào)用的 block @property (nonatomic, copy, nullable) LOTAnimationCompletionBlock completionBlock; /// 直接設(shè)定播放數(shù)據(jù),也就是解析 json 文件后的數(shù)據(jù)模型 @property (nonatomic, strong, nullable) LOTComposition *sceneModel;在上述屬性中我們可以看到一個(gè)緩存 cache 屬性,等一下我們將會(huì)一起來(lái)探究 Lottie 究竟是如何緩存動(dòng)畫(huà)的,緊接著我們來(lái)接著看 LOTAniamtionView 的 API 中所定義的部分方法:
/* * 這個(gè)方法表示 animation 將從當(dāng)前的 position 播放到指定的 progress * 當(dāng) loopAnimation 屬性為 YES 時(shí),這個(gè)動(dòng)畫(huà)將從當(dāng)前 position 到指定 progress 并且無(wú)限循環(huán) * 當(dāng) loopAnimation 屬性為 YES 時(shí),這個(gè)動(dòng)畫(huà)將從當(dāng)前 position 到指定 progress 然后停到 progress 的那一幀 * 當(dāng)動(dòng)畫(huà)完成后會(huì)調(diào)用指定 block */ - (void)playToProgress:(CGFloat)toProgress withCompletion:(nullable LOTAnimationCompletionBlock)completion; /* * 和上面的方法差不多,主要是開(kāi)始的 progress 也可以由我們來(lái)指定 */ - (void)playFromProgress:(CGFloat)fromStartProgress toProgress:(CGFloat)toEndProgress withCompletion:(nullable LOTAnimationCompletionBlock)completion; /* * 從當(dāng)前的 position 動(dòng)畫(huà)到指定的 frame */ - (void)playToFrame:(nonnull NSNumber *)toFrame withCompletion:(nullable LOTAnimationCompletionBlock)completion; /* * 設(shè)置開(kāi)始的 frame 并且動(dòng)畫(huà)到指定的 frame */ - (void)playFromFrame:(nonnull NSNumber *)fromStartFrame toFrame:(nonnull NSNumber *)toEndFrame withCompletion:(nullable LOTAnimationCompletionBlock)completion; /** * 從當(dāng)前的 position 完成到結(jié)束 position **/ - (void)playWithCompletion:(nullable LOTAnimationCompletionBlock)completion; /// 簡(jiǎn)單的播放到動(dòng)畫(huà)結(jié)尾,播放完成后會(huì)調(diào)用完成block - (void)play; /// 暫停動(dòng)畫(huà)并且調(diào)用完成block - (void)pause; /// 停止當(dāng)前動(dòng)畫(huà)并且倒回到最開(kāi)始的那一幀,完成后調(diào)用block - (void)stop; /// 設(shè)置當(dāng)前的動(dòng)畫(huà)到指定的 frame,如果當(dāng)前動(dòng)畫(huà)正在播放則會(huì)停止動(dòng)畫(huà)并且調(diào)用完成block - (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame; /// 強(qiáng)制對(duì)現(xiàn)在的 frame 進(jìn)行升級(jí) - (void)forceDrawingUpdate; /// 打印所有繼承鏈上的 keypath - (void)logHierarchyKeypaths;
Lottie 是如何緩存動(dòng)畫(huà)的
-
在 LOTAniamtionCache 的實(shí)現(xiàn)文件中可以看到以下方法:
// 表示是否支持緩存 - (void)setCacheEnable:(BOOL)cacheEnable { _cacheEnable = cacheEnable; if (!self.sceneModel.cacheKey) { return; } if (cacheEnable) { // 如果支持,則向 cache 中添加這個(gè)key所代表的對(duì)象已經(jīng)向字典中添加這個(gè) key 以及它對(duì)應(yīng)的 value 值,也就是動(dòng)畫(huà)數(shù)據(jù)對(duì)象 [[LOTAnimationCache sharedCache] addAnimation:_sceneModel forKey:self.sceneModel.cacheKey]; } else { // 如果不支持,則從字典中移除這個(gè) key,和這個(gè) key 所代表的對(duì)象,以及數(shù)組中的 key [[LOTAnimationCache sharedCache] removeAnimationForKey:self.sceneModel.cacheKey]; } } // 具體方法如下: // 可以看到,LOTAnimationCache 維護(hù)了一個(gè)添加 key 和 value 到字典和一個(gè)數(shù)組 @implementation LOTAnimationCache { NSMutableDictionary *animationsCache_; NSMutableArray *lruOrderArray_; } // 當(dāng)添加動(dòng)畫(huà)時(shí):首先需要判斷當(dāng)前數(shù)組的 size 是否已經(jīng)大于了最大的 size,如果是的話,則先清除最前面緩存的動(dòng)畫(huà),然后再添加新的動(dòng)畫(huà),而這個(gè) const NSInteger kLOTCacheSize = 50;最大值為 50 - (void)addAnimation:(LOTComposition *)animation forKey:(NSString *)key { if (lruOrderArray_.count >= kLOTCacheSize) { NSString *oldKey = lruOrderArray_[0]; [animationsCache_ removeObjectForKey:oldKey]; [lruOrderArray_ removeObject:oldKey]; } [lruOrderArray_ removeObject:key]; [lruOrderArray_ addObject:key]; [animationsCache_ setObject:animation forKey:key]; } // 當(dāng)移除動(dòng)畫(huà)時(shí):則直接將緩存有該動(dòng)畫(huà)的數(shù)組中移除這個(gè) key,并且在 cache 字典中也移除這個(gè) key 和它所對(duì)應(yīng)的對(duì)象。 - (void)removeAnimationForKey:(NSString *)key { [lruOrderArray_ removeObject:key]; [animationsCache_ removeObjectForKey:key]; }接下來(lái)在閱讀 LOTAniamtionCache 的實(shí)現(xiàn)文件,可以發(fā)現(xiàn),整個(gè) cache 是一個(gè)單例,也就是存在于 app 的整個(gè)生命周期中不會(huì)被銷(xiāo)毀,一旦 app 關(guān)閉,由于數(shù)據(jù)存儲(chǔ)也僅僅是簡(jiǎn)單的使用一個(gè)數(shù)組和一個(gè)字典來(lái)存儲(chǔ),并未進(jìn)行持久化處理,單例中所緩存的數(shù)據(jù)也會(huì)被銷(xiāo)毀,所以我們對(duì)于動(dòng)畫(huà)的緩存僅限于我們?cè)谑褂?app 時(shí)。
+ (instancetype)sharedCache { static LOTAnimationCache *sharedCache = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedCache = [[self alloc] init]; }); return sharedCache; } - (instancetype)init { self = [super init]; if (self) { animationsCache_ = [[NSMutableDictionary alloc] init]; lruOrderArray_ = [[NSMutableArray alloc] init]; } return self; }
總結(jié)
- 此次僅僅是從表面上簡(jiǎn)單的分析了 Lottie 這個(gè)庫(kù)是如何解析 json 文件,以及其常用的幾個(gè)方法和如何進(jìn)行動(dòng)畫(huà)緩存的,以后如果有時(shí)間的話,會(huì)繼續(xù)學(xué)習(xí)這個(gè)庫(kù)的實(shí)現(xiàn)。

