Lottie 是 Airbnb 开发的一款能够为原生应用添加动画效果的开源工具。Lottie 目前提供了 iOS, Android, 和 React Native 版本,能够实时渲染 After Effects 动画特效。
- Android : https://github.com/airbnb/lottie-android
- iOS : https://github.com/airbnb/lottie-ios
- React Native : https://github.com/airbnb/lottie-react-native
Lottie支持ios8以上系统,Lottie动画可以加载Bundled JSON文件或URL。目前,Lottie支持路径修剪,蒙版、遮盖等操作。
LOTAnimationView
加载LOTAnimationView动画.h
//加载本地json文件
+ (instancetype)animationNamed:(NSString *)animationName NS_SWIFT_NAME(init(name:));
+ (instancetype)animationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle NS_SWIFT_NAME(init(name:bundle:));
+ (instancetype)animationFromJSON:(NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));
//加载远程文件
- (instancetype)initWithContentsOfURL:(NSURL *)url;
@property (nonatomic, readonly) BOOL isAnimationPlaying;
@property (nonatomic, assign) BOOL loopAnimation;
@property (nonatomic, assign) CGFloat animationProgress;
@property (nonatomic, assign) CGFloat animationSpeed;
@property (nonatomic, readonly) CGFloat animationDuration;
- (void)playWithCompletion:(LOTAnimationCompletionBlock)completion;
- (void)play;
- (void)pause;
- (void)addSubview:(LOTView *)view
toLayerNamed:(NSString *)layer;
执行layer动画
+ (instancetype)animationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle {
NSArray *components = [animationName componentsSeparatedByString:@"."];
animationName = components.firstObject;
LOTComposition *comp = [[LOTAnimationCache sharedCache] animationForKey:animationName];
if (comp) {
return [[LOTAnimationView alloc] initWithModel:comp];
}
NSError *error;
NSString *filePath = [bundle pathForResource:animationName ofType:@"json"];
NSData *jsonData = [[NSData alloc] initWithContentsOfFile:filePath];
NSDictionary *JSONObject = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData
options:0 error:&error] : nil;
if (JSONObject && !error) {
LOTComposition *laScene = [[LOTComposition alloc] initWithJSON:JSONObject];
[[LOTAnimationCache sharedCache] addAnimation:laScene forKey:animationName];
return [[LOTAnimationView alloc] initWithModel:laScene];
}
NSException* resourceNotFoundException = [NSException exceptionWithName:@"ResourceNotFoundException"
reason:[error localizedDescription]
userInfo:nil];
@throw resourceNotFoundException;
}
LOTComposition(动画数据)
用于解析动画的json文件,获取整个动画数据
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
NSNumber *width = jsonDictionary[@"w"]; //动画的宽度
NSNumber *height = jsonDictionary[@"h"]; //动画的高度
if (width && height) {
CGRect bounds = CGRectMake(0, 0, width.floatValue, height.floatValue);
_compBounds = bounds; // 动画的bounds
}
_startFrame = [jsonDictionary[@"ip"] copy]; //动画的起始大小
_endFrame = [jsonDictionary[@"op"] copy]; //动画的结束大小
_framerate = [jsonDictionary[@"fr"] copy]; //动画的变化比率
if (_startFrame && _endFrame && _framerate) {
//通过动画起始和结束的大小和变化比率计算出动画时间
NSInteger frameDuration = _endFrame.integerValue - _startFrame.integerValue;
NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
_timeDuration = timeDuration;
}
NSArray *assetArray = jsonDictionary[@"assets"]; //动画的资产,一般为空
if (assetArray.count) {
_assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray];
}
NSArray *layersJSON = jsonDictionary[@"layers"]; //动画的layer层
if (layersJSON) {
_layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
withBounds:_compBounds
withFramerate:_framerate
withAssetGroup:_assetGroup];
}
[_assetGroup finalizeInitialization];
}
LOTLayerGroup(Layer动画组)
解析json文件中的"layers"
层的数据返回所有LOTLayer
集合
- (instancetype)initWithLayerJSON:(NSArray *)layersJSON
withBounds:(CGRect)bounds
withFramerate:(NSNumber *)framerate
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup {
self = [super init];
if (self) {
_framerate = framerate;
_bounds = bounds;
[self _mapFromJSON:layersJSON withAssetGroup:assetGroup];
}
return self;
}
- (void)_mapFromJSON:(NSArray *)layersJSON withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup {
NSMutableArray *layers = [NSMutableArray array];
NSMutableDictionary *modelMap = [NSMutableDictionary dictionary];
NSMutableDictionary *referenceMap = [NSMutableDictionary dictionary];
for (NSDictionary *layerJSON in layersJSON) {
LOTLayer *layer = [[LOTLayer alloc] initWithJSON:layerJSON
withCompBounds:_bounds
withFramerate:_framerate
withAssetGroup:assetGroup];
[layers addObject:layer];
modelMap[layer.layerID] = layer;
if (layer.referenceID) {
referenceMap[layer.referenceID] = layer;
}
}
_referenceIDMap = referenceMap;
_modelMap = modelMap;
_layers = layers;
}
// 根据layerID获取LOTLayer
- (LOTLayer *)layerModelForID:(NSNumber *)layerID {
return _modelMap[layerID];
}
// 根据referenceID获取LOTLayer
- (LOTLayer *)layerForReferenceID:(NSString *)referenceID {
return _referenceIDMap[referenceID];
}
LOTLayer
解析获得json文件中的每一个layer数据具体详情
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
withCompBounds:(CGRect)compBounds
withFramerate:(NSNumber *)framerate
withAssetGroup:(LOTAssetGroup *)assetGroup{
_parentCompBounds = compBounds;
_layerName = [jsonDictionary[@"nm"] copy]; // 名称
_layerID = [jsonDictionary[@"ind"] copy]; // ID
NSNumber *layerType = jsonDictionary[@"ty"]; // 类型
_layerType = layerType.integerValue;
if (jsonDictionary[@"refId"]) { // referenceID
_referenceID = [jsonDictionary[@"refId"] copy];
}
_parentID = [jsonDictionary[@"parent"] copy]; // 父类ID
_inFrame = [jsonDictionary[@"ip"] copy]; // 起始大小
_outFrame = [jsonDictionary[@"op"] copy]; // 结束大小
_framerate = framerate; // 变化比率
// layer类型
if (_layerType == LOTLayerTypePrecomp) { // 预补偿
_layerHeight = [jsonDictionary[@"h"] copy];
_layerWidth = [jsonDictionary[@"w"] copy];
[assetGroup buildAssetNamed:_referenceID
withBounds:CGRectMake(0, 0, _layerWidth.floatValue, _layerHeight.floatValue)
andFramerate:_framerate];
} else if (_layerType == LOTLayerTypeImage) { // 图片
[assetGroup buildAssetNamed:_referenceID
withBounds:CGRectZero
andFramerate:_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];
} else { // 其他
_layerWidth = @(compBounds.size.width);
_layerHeight = @(compBounds.size.height);
}
_layerBounds = CGRectMake(0, 0, _layerWidth.floatValue, _layerHeight.floatValue); // 获取layer的bounds
//帧动画属性
NSDictionary *ks = jsonDictionary[@"ks"];
NSDictionary *opacity = ks[@"o"]; // 透明度变化
if (opacity) {
_opacity = [[LOTAnimatableNumberValue alloc] initWithNumberValues:opacity frameRate:_framerate];
[_opacity remapValuesFromMin:@0 fromMax:@100 toMin:@0 toMax:@1];
}
NSDictionary *rotation = ks[@"r"]; // 旋转
if (rotation == nil) {
rotation = ks[@"rz"];
}
if (rotation) {
_rotation = [[LOTAnimatableNumberValue alloc] initWithNumberValues:rotation frameRate:_framerate];
[_rotation remapValueWithBlock:^CGFloat(CGFloat inValue) {
return LOT_DegreesToRadians(inValue);
}];
}
NSDictionary *position = ks[@"p"]; // 位置
if ([position[@"s"] boolValue]) {
// Separate dimensions
_positionX = [[LOTAnimatableNumberValue alloc] initWithNumberValues:position[@"x"] frameRate:_framerate];
_positionY = [[LOTAnimatableNumberValue alloc] initWithNumberValues:position[@"y"] frameRate:_framerate];
} else {
_position = [[LOTAnimatablePointValue alloc] initWithPointValues:position frameRate:_framerate];
}
NSDictionary *anchor = ks[@"a"]; // 锚点
if (anchor) {
_anchor = [[LOTAnimatablePointValue alloc] initWithPointValues:anchor frameRate:_framerate];
[_anchor remapPointsFromBounds:_layerBounds toBounds:CGRectMake(0, 0, 1, 1)];
_anchor.usePathAnimation = NO;
}
NSDictionary *scale = ks[@"s"]; // 缩放
if (scale) {
_scale = [[LOTAnimatableScaleValue alloc] initWithScaleValues:scale frameRate:_framerate];
}
_matteType = [jsonDictionary[@"tt"] integerValue]; // 磨砂
// 遮罩
NSMutableArray *masks = [NSMutableArray array];
for (NSDictionary *maskJSON in jsonDictionary[@"masksProperties"]) {
LOTMask *mask = [[LOTMask alloc] initWithJSON:maskJSON frameRate:_framerate];
[masks addObject:mask];
}
_masks = masks.count ? masks : nil;
// 形变
NSMutableArray *shapes = [NSMutableArray array];
for (NSDictionary *shapeJSON in jsonDictionary[@"shapes"]) {
id shapeItem = [LOTShapeGroup shapeItemWithJSON:shapeJSON frameRate:_framerate compBounds:_layerBounds];
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);
}
}
}
_hasInAnimation = _inFrame.integerValue > 0;
NSMutableArray *keys = [NSMutableArray array];
NSMutableArray *keyTimes = [NSMutableArray array];
CGFloat layerLength = _outFrame.integerValue;
_layerDuration = (layerLength / _framerate.floatValue);
if (_hasInAnimation) {
[keys addObject:@1];
[keyTimes addObject:@0];
[keys addObject:@0];
CGFloat inTime = _inFrame.floatValue / layerLength;
[keyTimes addObject:@(inTime)];
} else {
[keys addObject:@0];
[keyTimes addObject:@0];
}
[keys addObject:@1];
[keyTimes addObject:@1];
_inOutKeyTimes = keyTimes;
_inOutKeyframes = keys;
}
获取到layer动画的数据后,根据数据创建动画的layer层(LOTCompositionLayer)。