iOS && AndroidiOS Developer三方讲解

Lottie 动画原理(OC版)

2018-04-03  本文已影响178人  mkvege

Lottie 是一个很好的动画库,不同于FaceBook 的 POP,Lottie 主要是重现由AE(Adobe After Effects)实现的动画,具体方法是AE 导出一个json,Lottie 读取json 进行较为炫酷的动画。

lottie 动画

动画原理

动画原理图

动画原理图

为方便理解,做了一个草图。图片的右侧部分代表layer 的层级结构,左侧代表动画的进行过程。

从加载Json到展示动画的流程

Json 转 Model

Json 的第一个入口为 LOTComposition,以下是核心代码

- (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];
  _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;
  }
  //图片信息
  NSArray *assetArray = jsonDictionary[@"assets"];
  if (assetArray.count) {
    _assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
  }
  
 //所有子Layer 信息
  NSArray *layersJSON = jsonDictionary[@"layers"];
  if (layersJSON) {
    _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
                                            withAssetGroup:_assetGroup
                                             withFramerate:_framerate];
  }
  
  [_assetGroup finalizeInitializationWithFramerate:_framerate];
}

接下来进入每个子Layer 的内部 注释部分为Json里面对应的字段

- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
              withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
               withFramerate:(NSNumber *)framerate;

@property (nonatomic, readonly) NSString *layerName;                // nm
@property (nonatomic, readonly, nullable) NSString *referenceID;    //refid
@property (nonatomic, readonly) NSNumber *layerID;                  //ind
@property (nonatomic, readonly) LOTLayerType layerType;             //ty
@property (nonatomic, readonly, nullable) NSNumber *parentID;       //parent
@property (nonatomic, readonly) NSNumber *startFrame;               //st       
@property (nonatomic, readonly) NSNumber *inFrame;                  //ip
@property (nonatomic, readonly) NSNumber *outFrame;                 //op
@property (nonatomic, readonly) NSNumber *timeStretch;              //st        
@property (nonatomic, readonly) CGRect layerBounds;                 //来自 (0,0,w,h)

@property (nonatomic, readonly, nullable) NSArray<LOTShapeGroup *> *shapes; //shapes
@property (nonatomic, readonly, nullable) NSArray<LOTMask *> *masks;        //masksProperties

@property (nonatomic, readonly, nullable) NSNumber *layerWidth;             //w
@property (nonatomic, readonly, nullable) NSNumber *layerHeight;            //h
@property (nonatomic, readonly, nullable) UIColor *solidColor;              //sc
@property (nonatomic, readonly, nullable) LOTAsset *imageAsset;

//************LayerTransform变化的关键点LOTKeyframeGroup************//
@property (nonatomic, readonly) LOTKeyframeGroup *opacity;                  // o 不透明度
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *timeRemapping;  // tm
@property (nonatomic, readonly) LOTKeyframeGroup *rotation;                 // r rz
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *position;       // p

@property (nonatomic, readonly, nullable) LOTKeyframeGroup *positionX;    // 有缩放 s ,X
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *positionY;      //有缩放, Y

@property (nonatomic, readonly) LOTKeyframeGroup *anchor;  // a
@property (nonatomic, readonly) LOTKeyframeGroup *scale;    //s

@property (nonatomic, readonly) LOTMatteType matteType;    //tt

@end

每个Layer Model 中包含Layer的基本信息,transform变化需要的则是每个LOTKeyframeGroup 类型的属性。这里面包含了该Layer 的 transform变化的关键帧数组,而masks 和 shapes 的信息包含在上面的两个同名数组中。

动画显示

数据加载好了以后就需要进行动画显示,最底层的CompositionLayer通过继承CALayer ,添加Currentframe 属性,给这个属性添加一个CABaseAnimation 动画,然后重写CALayer的display方法,在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];
  }
//transform 更新
  if (_transformInterpolator && [_transformInterpolator hasUpdateForFrame:newFrame]) {
    _wrapperLayer.transform = [_transformInterpolator transformForFrame:newFrame];
  }

//内部shapes 更新
  [_contentsGroup updateWithFrame:newFrame withModifierBlock:nil forceLocalUpdate:forceUpdate];

//内部mask 更新
  _maskLayer.currentFrame = newFrame;
}

1.根据子Layer的起始帧和结束帧判断当前帧子Layer是否显示
2.更新子Layer当前帧的透明度
3.更新子Layer当前帧的transform
4.更新子Layer中路径和形状等内容的变化

Interpolator

上述2,3,4步都是通过Interpolator 这些类来从当前frame中计算出我们需要的值,如图,都在这里


Interpolator 文件目录

上面有提到 Layer 做动画有三个部分一是 Layer 本身的transform,二是Layer内部的shapes,第三是Layer内部的masks。由于它们的核心逻辑都来自于Interpolar ,所以这里只以Layer 本身的transform 变化为例。

所有的Interpolar 继承于 LOTValueInterpolator,他的头文件如下

- (instancetype)initWithKeyframes:(NSArray <LOTKeyframe *> *)keyframes;//需要变化位置的关键帧数组

@property (nonatomic, weak, nullable) LOTKeyframe *leadingKeyframe;//当前frame下的前一帧
@property (nonatomic, weak, nullable) LOTKeyframe *trailingKeyframe;//当前frame下的后一帧
@property (nonatomic, readonly) BOOL hasDelegateOverride;

- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate;

- (BOOL)hasUpdateForFrame:(NSNumber *)frame;//用于判断是否需要进行关键帧变化,如果当前帧候这个使用interpolator的控件不需要显示,则不需要进行更新
- (CGFloat)progressForFrame:(NSNumber *)frame;//获取当前帧下的进度。根据起始帧和结束帧计算出来

而transform变换需要很多的信息,在LOTTransformInterpolator中

@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;

包含这些会变化的关键帧(keyframe)数组。当传入当前frame时,这些interpolator会返回不同的数值,从而组成当前的transform。这些不同的Interpolar根据会根据自己的算法返回当前所需要的值,例如:LOTSizeInterpolator 会通过 size 关健帧数组 返回当前frame下的 cgsize。但是他们的大体计算流程是一样的。
1.在关键帧数组中找到当前frame的前一个关键帧(leadingKeyframe)和后一个关键帧(trailingKeyframe)
2.计算当前frame 在 leadingKeyframe 和 trailingKeyframe 的进度(progress)
3.根据这个progress以及 leadingKeyframe,trailingKeyframe算出当前frame下的值。(不同的interpolar算法不同)

如下图所示 lottieTransform变化流程

最后放一下LOTPointInterpolator中根据frame 计算 当前point 的代码

- (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;
}
上一篇下一篇

猜你喜欢

热点阅读