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

例如针对LOTLayerTypePrecompLOTLayerTypeImage 就要进行解析操作 查找其中的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;

加载

  1. bundle中加载

    + (nullable instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
        LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:bundle];
    }
  2. 文件中加载

    + (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;
    }
  3. 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];
    }

ImageCache