ege动画_Lottie 动画原理

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

lottie 动画

动画原理

一个完整动画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 *shapes; //shapes

@property (nonatomic, readonly, nullable) NSArray *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 *)keyframes;//需要变化位置的关键帧数组

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

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

@property (nonatomic, readonly) BOOL hasDelegateOverride;

- (void)setValueDelegate:(id _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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值