Lottie 动画原理(OC版)
Lottie 是一个很好的动画库,不同于FaceBook 的 POP,Lottie 主要是重现由AE(Adobe After Effects)实现的动画,具体方法是AE 导出一个json,Lottie 读取json 进行较为炫酷的动画。
动画原理
- 一个完整动画View由很多个子Layer 组成,每个子Layer中主要通过shapes(形状),masks(蒙版),transform三大部分进行动画。
- 通过读取Json文件 可以获取到每个子Layer 的shapes,masks,以及出现时间,消失时间,Transform 各个属性的关键帧数组
- 动画通过给CompositionLayer (所有的子layer都添加在这个Layer 上)的 “CurrentFrame” 属性添加一个CABaseAnimation 来实现
- 所有的子Layer根据CurrentFrame 属性的变化,根据Json中的关键帧数组计算出自己的当前状态进行显示。(下面有介绍计算方法)
动画原理图
动画原理图为方便理解,做了一个草图。图片的右侧部分代表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算法不同)
最后放一下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;
}