Lottie源代码解析

Lottie-iOS

Lottie动画的原理:

  1. 一个完整动画View,是由很多个子Layer 组成,而每个子Layer主要通过shapes(形状),masks(蒙版),transform三大部分进行动画。
  2. Lottie框架通过读取JSON文件,获取到每个子Layer 的shapes,masks,以及出现时间,消失时间以及Transform各个属性的关键帧数组。
  3. 动画则是通过给CompositionLayer (所有的子layer都添加在这个Layer 上)的 currentFrame属性添加一个CABaseAnimation 来实现。
  4. 所有的子Layer根据currentFrame属性的变化,根据JSON中的关键帧数组计算出自己的当前状态并进行显示。

Lottie的创建方法:

/// 默认从main bundle中加载json文件和图片,animationName实际上就是json文件的名字
-(nonnull instancetype)animationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(init(name:));
/// 从指定的bundle中加载json文件和图片
-(nonnull instancetype)animationNamed:(nonnull NSString )animationName inBundle:(nonnull NSBundle )bundle NS_SWIFT_NAME(init(name:bundle:));
///直接从给定的json文件中加载动画,默认从main bundle加载
-(nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));
/// 从一个文件URL加载动画,但是不能使用web url作为参数
-(nonnull instancetype)animationWithFilePath:(nonnull NSString *)filePath NS_SWIFT_NAME(init(filePath:));
/// 给定一个反序列化的json文件数据和一个特定的bundle来初始化加载动画
-(nonnull instancetype)animationFromJSON:(nullable NSDictionary )animationJSON inBundle:(nullable NSBundle )bundle NS_SWIFT_NAME(init(json:bundle:));
/// 直接使用LOTComposition来创建动画, 图片从指定的bundle中加载
-(nonnull instancetype)initWithModel:(nullable LOTComposition )model inBundle:(nullable NSBundle )bundle;
/// 异步的从指定的URL中加载动画,这个url为webUrl
-(nonnull instancetype)initWithContentsOfURL:(nonnull NSURL *)url;

Lottie的使用:

/* 
 * 将动画从其当前位置播放到特定进度。
 * 动画将从其当前位置开始。
 * 如果循环动画,则动画将从开始位置无限循环到进度。
 * 如果 loopAnimation 为 NO,则动画将停止并调用完成块。
 */
- (void)playToProgress:(CGFloat)toProgress
        withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/*
 * 播放从特定进度到特定进度的动画
 * 动画将从其当前位置开始。
 * 如果循环动画,则动画将从开始无限期地循环到结束进度
 * 如果 loopAnimation 为 NO,则动画将停止并调用完成块。
 */
- (void)playFromProgress:(CGFloat)fromStartProgress
              toProgress:(CGFloat)toEndProgress
          withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/*
 * Plays the animation from its current position to a specific frame.
 * The animation will start from its current position.
 * If loopAnimation is YES the animation will loop from beginning to toFrame indefinitely.
 * If loopAnimation is NO the animation will stop and the completion block will be called.
 */
- (void)playToFrame:(nonnull NSNumber *)toFrame
     withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/*
 * 将动画从特定帧播放到特定帧。
 * 动画将从其当前位置开始。
 * 如果循环动画,则动画将无限期地循环开始帧到结束帧。
 * 如果 loopAnimation 为 NO,则动画将停止并调用完成块。
 */
- (void)playFromFrame:(nonnull NSNumber *)fromStartFrame
              toFrame:(nonnull NSNumber *)toEndFrame
       withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/**
 * 播放动画从当前位置到动画结束。
 * 动画将从其当前位置开始。
 * 如果循环动画,则动画将从头到尾无限循环。
 * 如果 loopAnimation 为 NO,则动画将停止并调用完成块。
 **/
- (void)playWithCompletion:(nullable LOTAnimationCompletionBlock)completion;

/// 播放
- (void)play;

/// 暂停到当前帧,调用完成块。
- (void)pause;

/// 停止,回到开头,调用完成块。
- (void)stop;

/// 将动画进度设置为特定帧。如果动画正在播放,它将停止并调用完成块。
- (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame;

/// 将动画进度设置为特定帧。如果动画正在播放,它将停止,如果调用完成为是,则将调用完成块。
- (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame callCompletionIfNecessary:(BOOL)callCompletion;

Lottie部分源码解析:

Lottie源代码解析_第1张图片
LOTAnimationView.m中初始化方法最后会调用下面这个方法:

- (instancetype)initWithModel:(LOTComposition *)model inBundle:(NSBundle *)bundle {
  self = [self initWithFrame:model.compBounds];
  if (self) {
    _bundle = bundle;
    [self _initializeAnimationContainer];
    [self _setupWithSceneModel:model];
  }
  return self;
}

LOTComposition对象包含一个LOTAssetGroup对象,该对象用于管理动画中使用的所有资源。LOTAssetGroup对象包含一个或多个LOTAsset对象,每个LOTAsset对象代表一个资源文件。例如,当动画中使用了一个图片资源时,LOTAssetGroup会创建一个LOTImageAsset对象来管理该图片资源。
LOTComposition还包含一个LOTLayerGroup,该对象用于管理所有图层和子图层。LOTLayerGroup对象包含一个或多个LOTLayer,每个LOTLayer代表一个图层。LOTLayer包含了图层的各种属性和元素,例如位置、大小、旋转、透明度等等。
每个LOTLayer都可以包含一个或多个子图层,每个子图层都可以包含一个或多个子视图。这种嵌套关系使得CALayer可以创建复杂的视图层级结构,并实现高效的渲染过程。
在Lottie中,LOTComposition被转换为一个名为LOTCompositionContainer的模型对象。LOTCompositionContainer包含了动画的各种属性和元素,例如图层、路径、形状、颜色等等。此外,LOTCompositionContainer还包含了一个CALayer用于渲染动画。

LOTComposition

可以在LOTComposition的代码里面找到Lottie解析json文件的过程:

#pragma mark - Initializer 基础初始化方法,json和bundle
- (instancetype _Nonnull)initWithJSON:(NSDictionary * _Nullable)jsonDictionary
                      withAssetBundle:(NSBundle * _Nullable)bundle {
  self = [super init];
  if (self) {
    if (jsonDictionary) {
      [self _mapFromJSON:jsonDictionary withAssetBundle:bundle];
    }
  }
  return self;
}

#pragma mark - Internal Methods 内部方法,用来解析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;
  }
  // 动画开始frame
  _startFrame = [jsonDictionary[@"ip"] copy];
  // 动画结束frame
  _endFrame = [jsonDictionary[@"op"] copy];
  // 动画变化率
  _framerate = [jsonDictionary[@"fr"] copy];
  
  if (_startFrame && _endFrame && _framerate) {
    NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1;
    NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
    // 计算动画duration
    _timeDuration = timeDuration;
  }
  
  NSArray *assetArray = jsonDictionary[@"assets"];
  if (assetArray.count) {
    _assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
  }
  
  NSArray *layersJSON = jsonDictionary[@"layers"];
  if (layersJSON) {
    _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
                                            withAssetGroup:_assetGroup
                                             withFramerate:_framerate];
  }
  
  [_assetGroup finalizeInitializationWithFramerate:_framerate];
}

在这个方法中,它调用了LOTAssetGroup的图片数组的初始化方法和LOTLayerGroup的layer层数组的初始化方法,生成了_assetGroup并使用其初始化了_layerGroup。具体来看一下。

LOTAssetGroup

LOTAssetGroup的初始化:

- (instancetype _Nonnull)initWithJSON:(NSArray * _Nonnull)jsonArray
                      withAssetBundle:(NSBundle * _Nullable)bundle
                        withFramerate:(NSNumber * _Nonnull)framerate {
  self = [super init];
  if (self) {
    _assetBundle = bundle;
    _assetMap = [NSMutableDictionary dictionary];
    NSMutableDictionary *assetJSONMap = [NSMutableDictionary dictionary];
    for (NSDictionary<NSString *, NSString *> *assetDictionary in jsonArray) {
      NSString *referenceID = assetDictionary[@"id"];
      if (referenceID) {
        assetJSONMap[referenceID] = assetDictionary;
      }
    }
    _assetJSONMap = assetJSONMap;
  }
  return self;
}

assetJSONMap存放的数据,里面是图片的各种信息:

{
    "image_0" : {"id":"image_0","w":180,"h":180,"u":"images/","p":"img_0.png","e":0},
    "image_1" : {"id":"image_1","w":600,"h":600,"u":"images/","p":"img_1.png","e":0},
    ...
}

我们最终在LOTAsset文件中找到了解析json数据的方法:

- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
      withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
       withFramerate:(NSNumber *)framerate {
  _referenceID = [jsonDictionary[@"id"] copy];
  
  if (jsonDictionary[@"w"]) { // 宽度
    _assetWidth = [jsonDictionary[@"w"] copy];
  }
  
  if (jsonDictionary[@"h"]) { // 高度
    _assetHeight = [jsonDictionary[@"h"] copy];
  }
  
  if (jsonDictionary[@"u"]) { // 图片路径(图片文件夹)
    _imageDirectory = [jsonDictionary[@"u"] copy];
  }
  
  if (jsonDictionary[@"p"]) { // 图片名
    _imageName = [jsonDictionary[@"p"] copy];
  }

  NSArray *layersJSON = jsonDictionary[@"layers"];
  if (layersJSON) {
    _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
                                            withAssetGroup:assetGroup
                                             withFramerate:framerate];
  }
}

LOTALayerGroup

LOTALayerGroup对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];
    if (layer.layerID) {
      modelMap[layer.layerID] = layer;
    }
    if (layer.referenceID) {
      referenceMap[layer.referenceID] = layer;
    }
  }
  
  _referenceIDMap = referenceMap;
  _modelMap = modelMap;
  _layers = layers;
}

里面调用了LOTALayer的初始化方法:

- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
      withAssetGroup:(LOTAssetGroup *)assetGroup
       withFramerate:(NSNumber *)framerate {

  _layerName = [jsonDictionary[@"nm"] copy]; // layer名
  _layerID = [jsonDictionary[@"ind"] copy]; // layer的id,表示这是第几个layer
  
  NSNumber *layerType = jsonDictionary[@"ty"]; // 表示layer的类型,这个变量是一个枚举类型
  _layerType = layerType.integerValue;
 // LOTLayerTypePrecomp, 
 // LOTLayerTypeSolid, 
 // LOTLayerTypeImage, 
 // LOTLayerTypeNull, 
 // LOTLayerTypeShape, 
 // LOTLayerTypeUnknown
 
  if (jsonDictionary[@"refId"]) {
  // 这里的refId和图片文件的referenceID指向的是同一个标识符,
  // 表示这个layer动画会作用在 referenceID 指向的图片上
    _referenceID = [jsonDictionary[@"refId"] copy];
  }
  // 父layer
  _parentID = [jsonDictionary[@"parent"] copy];
  
  if (jsonDictionary[@"st"]) {
    // 开始的 frame
    _startFrame = [jsonDictionary[@"st"] copy];
  }
  // 开始的 frame,通常和 startFrame 值相同
  _inFrame = [jsonDictionary[@"ip"] copy];
  //最后一帧的frame
  _outFrame = [jsonDictionary[@"op"] copy];
  if (jsonDictionary[@"sr"]) {
    _timeStretch = [jsonDictionary[@"sr"] copy];
  } else {
    _timeStretch = @1;
  }

  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"]; // 旋转 
  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"]; // 锚点
  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);
      }
    }
  }
}

LOTCompositionContainer

Lottie的整个动画,就是一个自定义属性的CAAnimation动画,自定义的属性就是currentFrame,这个动画是添加到LOTCompositionContainer上的。而LOTCompositionContainerLOTLayerContainer的子类,内部通过差值计算来得出对应帧在不同时间的值。

_compContainer = [[LOTCompositionContainer alloc] initWithModel:nil inLayerGroup:nil withLayerGroup:_sceneModel.layerGroup withAssestGroup:_sceneModel.assetGroup];
child = [[LOTLayerContainer alloc] initWithModel:layer inLayerGroup:childGroup];

不断的对子Layer预合成。
LOTLayerContainer初始化代码如下:

- (void)commonInitializeWith:(LOTLayer *)layer
                inLayerGroup:(LOTLayerGroup *)layerGroup {
  if (layer == nil) {
    return;
  }
  _layerName = layer.layerName;
  if (layer.layerType == LOTLayerTypeImage ||
      layer.layerType == LOTLayerTypeSolid ||
      layer.layerType == LOTLayerTypePrecomp) {
    _wrapperLayer.bounds = CGRectMake(0, 0, layer.layerWidth.floatValue, layer.layerHeight.floatValue);
    _wrapperLayer.anchorPoint = CGPointMake(0, 0);
    _wrapperLayer.masksToBounds = YES;
    DEBUG_Center.position = LOT_RectGetCenterPoint(self.bounds);
  }
  
  if (layer.layerType == LOTLayerTypeImage) {
    [self _setImageForAsset:layer.imageAsset];
  }
  
  _inFrame = [layer.inFrame copy];
  _outFrame = [layer.outFrame copy];

  _timeStretchFactor = [layer.timeStretch copy];
  _transformInterpolator = [LOTTransformInterpolator transformForLayer:layer];

  if (layer.parentID != nil) {
    NSNumber *parentID = layer.parentID;
    LOTTransformInterpolator *childInterpolator = _transformInterpolator;
    while (parentID != nil) {
      LOTLayer *parentModel = [layerGroup layerModelForID:parentID];
      LOTTransformInterpolator *interpolator = [LOTTransformInterpolator transformForLayer:parentModel];
      childInterpolator.inputNode = interpolator;
      childInterpolator = interpolator;
      parentID = parentModel.parentID;
    }
  }
  _opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:layer.opacity.keyframes];
  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;
  }
  
  NSMutableDictionary *interpolators = [NSMutableDictionary dictionary];
  // 设置属性
  _valueInterpolators = interpolators;
}

有图动画设置图片方法:

- (void)_setImageForAsset:(LOTAsset *)asset {
  self.asyncLoadingResource = NO;
  
  if (asset.imageName) {
    UIImage *image;
    if ([asset.imageName hasPrefix:@"data:"]) {
      // Contents look like a data: URL. Ignore asset.imageDirectory and simply load the image directly.
      NSURL *imageUrl = [NSURL URLWithString:asset.imageName];
      NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
      image = [UIImage imageWithData:imageData];
    } else if (asset.rootDirectory.length > 0) {
    // 有rootDirectory
      NSString *rootDirectory  = asset.rootDirectory;
      if (asset.imageDirectory.length > 0) {
      // 拼接图片资源“p”字段,图片文件名
        rootDirectory = [rootDirectory stringByAppendingPathComponent:asset.imageDirectory];
      }
      NSString *imagePath = [rootDirectory stringByAppendingPathComponent:asset.imageName];
        
      id<LOTImageCache> imageCache = [LOTCacheProvider imageCache];
      // 存在imageCache
      if (imageCache) {
        image = [imageCache imageForKey:imagePath];
        if (!image) {
          image = [UIImage imageWithContentsOfFile:imagePath];
          [imageCache setImage:image forKey:imagePath];
        }
      } else {
        image = [UIImage imageWithContentsOfFile:imagePath];
      }
    } else if (!asset.ignoreBundleResource) {
        NSString *imagePath = [asset.assetBundle pathForResource:asset.imageName ofType:nil];
        image = [UIImage imageWithContentsOfFile:imagePath];
    }

    //try loading from asset catalogue instead if all else fails
    if (!image && !asset.ignoreBundleResource) {
      image = [UIImage imageNamed:asset.imageName inBundle:asset.assetBundle compatibleWithTraitCollection:nil];
    }
    
    if (image) {
      _wrapperLayer.contents = (__bridge id _Nullable)(image.CGImage);
    } else if (asset.baseURL) {
        // 通过url下载
    } else {
      NSLog(@"%s: Warn: image not found: %@", __PRETTY_FUNCTION__, asset.imageName);
    }
  }
}

播放:

  • layer首次加载时会调用 +(BOOL)needsDisplayForKey:(NSString *)key方法来判断当前指定的属性key改变是否需要重新绘制,默认返回NO
  • 当Core Animartion中的key或者keypath等于+(BOOL)needsDisplayForKey:(NSString *)key 方法中指定的key,便会自动调用setNeedsDisplay方法
  • 当指定key发生更改时,会触发actionForKey
  • runloop是一个循环处理事件和消息的方法,CATransaction begin和 CATransaction commit 进行修改和提交新事务。
  • 每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用[CATranscation begin]开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画,在runloop快结束时,它会调用下一个事务display,也就是隐式动画
  • CALayer方法重绘响应链
    [layer setNeedDisplay] -> [layer displayIfNeed] -> [layer display] -> [layerDelegate displayLayer:]
    [layer setNeedDisplay] -> [layer displayIfNeed] -> [layer display] -> [layer drawInContext:] -> [layerDelegate drawLayer: inContext:]

LOTLayerContainer里面可以看到needsDisplayForKey指定了keycurrentFrame时触发重绘。actionForKey是接收指定key被修改时触发的行为操作,在下面代码中看到当keycurrentFrame时添加一个CABasicAnimation动画。

+ (BOOL)needsDisplayForKey:(NSString *)key {
  if ([key isEqualToString:@"currentFrame"]) {
    return YES;
  }
  return [super needsDisplayForKey:key];
}
- (id<CAAction>)actionForKey:(NSString *)event {
  if ([event isEqualToString:@"currentFrame"]) {
    CABasicAnimation *theAnimation = [CABasicAnimation
                                      animationWithKeyPath:event];
    theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    theAnimation.fromValue = [[self presentationLayer] valueForKey:event];
    return theAnimation;
  }
  return [super actionForKey:event];
}
 - (void)playFromFrame:(nonnull NSNumber *)fromStartFrame
              toFrame:(nonnull NSNumber *)toEndFrame
       withCompletion:(nullable LOTAnimationCompletionBlock)completion {
  if (_isAnimationPlaying) {
    return;
  }
  _playRangeStartFrame = fromStartFrame;
  _playRangeEndFrame = toEndFrame;
  if (completion) {
    self.completionBlock = completion;
  }
  if (!_sceneModel) {
    _isAnimationPlaying = YES;
    return;
  }

  BOOL playingForward = ((_animationSpeed > 0) && (toEndFrame.floatValue > fromStartFrame.floatValue))
    || ((_animationSpeed < 0) && (fromStartFrame.floatValue > toEndFrame.floatValue));

  CGFloat leftFrameValue = MIN(fromStartFrame.floatValue, toEndFrame.floatValue);
  CGFloat rightFrameValue = MAX(fromStartFrame.floatValue, toEndFrame.floatValue);

  NSNumber *currentFrame = [self _frameForProgress:_animationProgress];

  currentFrame = @(MAX(MIN(currentFrame.floatValue, rightFrameValue), leftFrameValue));

  if (currentFrame.floatValue == rightFrameValue && playingForward) {
    currentFrame = @(leftFrameValue);
  } else if (currentFrame.floatValue == leftFrameValue && !playingForward) {
    currentFrame = @(rightFrameValue);
  }
  _animationProgress = [self _progressForFrame:currentFrame];
  
  CGFloat currentProgress = _animationProgress * (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue);
  CGFloat skipProgress;
  if (playingForward) {
    skipProgress = currentProgress - leftFrameValue;
  } else {
    skipProgress = rightFrameValue - currentProgress;
  }
  NSTimeInterval offset = MAX(0, skipProgress) / _sceneModel.framerate.floatValue;
  if (!self.window) {
    _shouldRestoreStateWhenAttachedToWindow = YES;
    _completionBlockToRestoreWhenAttachedToWindow = self.completionBlock;
    self.completionBlock = nil;
  } else {
    float repeatCount = self.repeatCount == 0 ? HUGE_VALF : self.repeatCount;
    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 ? repeatCount : 1;
    animation.autoreverses = _autoReverseAnimation;
    animation.delegate = self;
    animation.removedOnCompletion = NO;
    if (offset != 0) {
      CFTimeInterval currentTime = CACurrentMediaTime();
      CFTimeInterval currentLayerTime = [self.layer convertTime:currentTime fromLayer:nil];
      animation.beginTime = currentLayerTime - (offset * 1 / _animationSpeed);
    }
    [_compContainer addAnimation:animation forKey:kCompContainerAnimationKey];
    _compContainer.shouldRasterize = NO;
  }
  _isAnimationPlaying = YES;
}

最底层的LOTLayerContainer继承自CALayer,添加了currentFrame属性,LOTCompositionContainer又是继承自LOTLayerContainer,为LOTCompositionContainer对象添加了一个CABaseAnimation动画,然后重写CALayerdisplay方法,在display方法中通过 CALayer中的presentationLayer获取在动画中变化的currentFrame数值 ,再通过遍历每一个子layer,将更新后的currentFrame传入,来实时更新每一个子layer的显示。核心代码在LOTLayerContainer中。

- (void)displayWithFrame:(NSNumber *)frame forceUpdate:(BOOL)forceUpdate {
  NSNumber *newFrame = @(frame.floatValue / self.timeStretchFactor.floatValue);
  if (ENABLE_DEBUG_LOGGING) NSLog(@"View %@ Displaying Frame %@, with local time %@", self, frame, newFrame);
  BOOL hidden = NO;
  if (_inFrame && _outFrame) {
    hidden = (frame.floatValue < _inFrame.floatValue ||
              frame.floatValue > _outFrame.floatValue);
  }
  self.hidden = hidden;
  if (hidden) {
    return;
  }
  if (_opacityInterpolator && [_opacityInterpolator hasUpdateForFrame:newFrame]) {
    self.opacity = [_opacityInterpolator floatValueForFrame:newFrame];
  }
  if (_transformInterpolator && [_transformInterpolator hasUpdateForFrame:newFrame]) {
    _wrapperLayer.transform = [_transformInterpolator transformForFrame:newFrame];
  }
  // 更新contentsGroup
  [_contentsGroup updateWithFrame:newFrame withModifierBlock:nil forceLocalUpdate:forceUpdate];
  _maskLayer.currentFrame = newFrame;
}

它实际上完成了以下几件事:

  1. 根据子Layer的起始帧和结束帧判断当前帧子Layer是否显示
  2. 更新子Layer当前帧的透明度
  3. 更新子Layer当前帧的transform
  4. 更新子Layer中路径和形状等内容的变化
    上面动画显示的2,3,4步都是通过XXInterpolator这种类,来从当前frame中计算出我们需要的值。下面看一个示例:
@interface LOTTransformInterpolator : NSObject
// 。。。
@property (nonatomic, readonly) LOTPointInterpolator *positionInterpolator;
@property (nonatomic, readonly) LOTPointInterpolator *anchorInterpolator;
@property (nonatomic, readonly) LOTSizeInterpolator *scaleInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *rotationInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *positionXInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *positionYInterpolator;
@property (nonatomic, strong, nullable) NSString *parentKeyName;
// 。。。

针对transform变换需要很多的信息,LOTTransformInterpolator中提供了这些所需的信息。

- (CATransform3D)transformForFrame:(NSNumber *)frame {
  CATransform3D baseXform = CATransform3DIdentity;
  if (_inputNode) {
    baseXform = [_inputNode transformForFrame:frame];
  }
  CGPoint position = CGPointZero;
  if (_positionInterpolator) {
    position = [_positionInterpolator pointValueForFrame:frame];
  }
  if (_positionXInterpolator &&
      _positionYInterpolator) {
    position.x = [_positionXInterpolator floatValueForFrame:frame];
    position.y = [_positionYInterpolator floatValueForFrame:frame];
  }
  CGPoint anchor = [_anchorInterpolator pointValueForFrame:frame];
  CGSize scale = [_scaleInterpolator sizeValueForFrame:frame];
  CGFloat rotation = [_rotationInterpolator floatValueForFrame:frame];
  CATransform3D translateXform = CATransform3DTranslate(baseXform, position.x, position.y, 0);
  CATransform3D rotateXform = CATransform3DRotate(translateXform, rotation, 0, 0, 1);
  CATransform3D scaleXform = CATransform3DScale(rotateXform, scale.width, scale.height, 1);
  CATransform3D anchorXform = CATransform3DTranslate(scaleXform, -1 * anchor.x, -1 * anchor.y, 0);
  return anchorXform;
}

插值计算过程:

// 根据前一帧与后一帧进行计算
- (CGPoint)pointValueForFrame:(NSNumber *)frame {
  CGFloat progress = [self progressForFrame:frame];
  CGPoint returnPoint;
  if (progress == 0) {
    returnPoint = self.leadingKeyframe.pointValue;
  } else if (progress == 1) {
    returnPoint = self.trailingKeyframe.pointValue;
  } else if (!CGPointEqualToPoint(self.leadingKeyframe.spatialOutTangent, CGPointZero) ||
             !CGPointEqualToPoint(self.trailingKeyframe.spatialInTangent, CGPointZero)) {
    // Spatial Bezier path
    CGPoint outTan = LOT_PointAddedToPoint(self.leadingKeyframe.pointValue, self.leadingKeyframe.spatialOutTangent);
    CGPoint inTan = LOT_PointAddedToPoint(self.trailingKeyframe.pointValue, self.trailingKeyframe.spatialInTangent);
    returnPoint = LOT_PointInCubicCurve(self.leadingKeyframe.pointValue, outTan, inTan, self.trailingKeyframe.pointValue, progress);
  } else {
    returnPoint = LOT_PointInLine(self.leadingKeyframe.pointValue, self.trailingKeyframe.pointValue, progress);
  }
  if (self.hasDelegateOverride) {
    return [self.delegate pointForFrame:frame.floatValue
                          startKeyframe:self.leadingKeyframe.keyframeTime.floatValue
                            endKeyframe:self.trailingKeyframe.keyframeTime.floatValue
                   interpolatedProgress:progress
                             startPoint:self.leadingKeyframe.pointValue
                               endPoint:self.trailingKeyframe.pointValue
                           currentPoint:returnPoint];
  }
  return returnPoint;
}
CGPoint LOT_PointInLine(CGPoint A, CGPoint B, CGFloat T) {
  CGPoint C;
  C.x = A.x - ((A.x - B.x) * T);
  C.y = A.y - ((A.y - B.y) * T);
  return C;
}

当传入当前frame时,这些interpolator会返回不同的数值,从而组成当前的transform。这些不同的Interpolar会根据自己的算法返回当前所需要的值,但是他们大体的流程都是一样的:

  1. 在关键帧数组中找到当前frame的前一个关键帧(leadingKeyframe)和后一个关键帧(trailingKeyframe)
  2. 计算当前frame 在 leadingKeyframe 和 trailingKeyframe 的进度(progress)
  3. 根据这个progress以及 leadingKeyframe,trailingKeyframe算出当前frame下的值。(不同的Interpolator算法不同

小结:

设计师做设计,开发者写实现,各司其职。
Lottie内部帮我们做了json文件映射到不同类的不同属性中,通过一系列的计算,确定出每一帧的数据,然后完美的显示在屏幕上。开发者只需要通过创建方法创建、播放方法播放,简单几行代码,就可以实现炫酷的动画。

你可能感兴趣的:(ios,objective-c,动画)