Lottie动画使用及原理分析

2023-05-16

1.Lottie是什么?

Lottie是Airbnb开源的一个动画渲染库,支持多平台,包括iOS、Android、React Native以及Flutter(https://github.com/airbnb/lottie-ios)。除了官方支持的平台,更有大神实现了支持Windows、Qt、Skia以及React、Vue、Angular等平台,感兴趣的可以去github搜罗一番。

Lottie动画产生的流程如下:
Lottie动画产生流程

整体工作流程为:
1)动效设计师使用After Effects制作动画,然后使用Bodymovin导出JSON文件,可以将JSON文件放到Bodymovin网站上运行看效果,也可以放在lottiefiles网站上运行看效果,而且lottiefiles有很多免费动画JSON资源可以下载看。
2)各个端使用对应的LottieSDK加载JSON文件,实现动画效果。

注意点:Lottie 3.0之后已经全部使用swift实现,所以如果需要使用Objective-C版本需要使用Lottie 2.5.3版本!

2.为什么使用Lottie?

Lottie出现之前:
1> 使用GIF,占用内存大,某些动画显示效果需要进行屏幕适配,安卓原生不支持GIF动画显示。
2> 使用帧动画,同样占用空间大,依然有屏幕适配的问题。
3> 原生实现组合式动画,需要写打量代码实现复杂动画效果,对技术要求比较高。

Lottie可以解决的问题:
1> 开发人员无需编写动画,只需加载
2> 多平台支持,一次设计多端使用
3> 解决设计提供的动效与实现不一致问题
4> 因为只是加载json文件,占用空间更小
5> 专业的人做专业的事,设计师安心做酷炫动画,开发者专心写逻辑

3.Lottie适用于哪些场景?

首先不是所有的动画都可以用Lottie来实现,一些通过属性动画实现的简单动画不需要Lottie实现,或者是有交互的动画Lottie实现不了。我们使用Lottie动画可以替代一些用代码实现很复杂的不带交互的动效,替代GIF动画和帧动画,具体可以使用在以下场景:

1> 启动(splash)动画:典型场景是APP logo动画的播放
2> 上下拉刷新(refresh)动画:所有APP都必备的功能,利用 Lottie可以做的更加简单酷炫
3> 加载(loading)动画:典型场景是网络请求的loading动画
4> 提示(tips)动画:典型场景是空白页的提示
5> 按钮(button)动画:典型场景如switch按钮、编辑按钮、播放按钮等按钮的动画
6> 礼物(gift)动画:典型场景是直播类APP的高级动画播放
等等。。。

4.Lottie的使用和原理

4.1 Lottie使用

文件目录

如上图,首先第一步,将动效师输出的json文件以及需要的images文件添加到工程中(建议使用单独文件夹专门进行Lottie源文件的管理),导出的json文件名称默认都是data.json,需要我们根据功能重新命名。

我们先来感受一下这个json文件是个什么样,这只是一个json文件的一部分而已哦
image.png

接下来需要写代码来加载动画。

1)最简单的加载方式:

LOTAnimationView *animation = [LOTAnimationView animationNamed:@"data"];
[self.view addSubview:animation];
[animation playWithCompletion:^(BOOL animationFinished) {
  // Do Something when finished
}];

2)通过url来加载:

这种情况一般是将动画效果存储在服务端或者从LottieFiles网站动态加载动效。

LOTAnimationView *animation = [[LOTAnimationView alloc] initWithContentsOfURL:[NSURL URLWithString:URL]];
[self.view addSubview:animation];

3)通过xib或者sb来加载:

storyboard加载

如上图的viewA,指定viewA类型为LOTAnimationView,并在箭头所指处设置animationName为对应的动画json文件名,代码中只需要调用play即可开始执行动画。

4)当然还有很多其他的方式加载lottie动画

/// 从一个反序列化的json字典加载
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));

/// 从一个特定的文件路径加载,但注意不是网络url
+ (nonnull instancetype)animationWithFilePath:(nonnull NSString *)filePath NS_SWIFT_NAME(init(filePath:));

/// 使用LOTComposition创建animation,从特定bundle中加载需要的图片资源
- (nonnull instancetype)initWithModel:(nullable LOTComposition *)model inBundle:(nullable NSBundle *)bundle;

5)我们可以控制动画的进度,制作有交互的动效

CGPoint translation = [gesture getTranslationInView:self.view];
CGFloat progress = translation.y / self.view.bounds.size.height;
animationView.animationProgress = progress;

除了控制进度,lottie中还有许多的属性提供给我们以控制动画,包括动画速度、持续时长、是否重复执行、是否反向执行、是否缓存动画等,查看LOTAnimationView头文件就能找到它们。

4.2 Lottie原理

接下来就是我们的重头戏,这么6的动画框架,它是怎么实现的呢?

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中的关键帧数组计算出自己的当前状态并进行显示。

接下来让我们深入它的源码(OC版本)去看看,对它的原理有一个更深刻的认识

1)入口类为LOTAnimationView,提供了一系列加载和设置动画的方法及属性以及对动画的操作,这里列举一二

...
+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(init(name:));
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));
+ (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON 
inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(init(json:bundle:));
...
@property (nonatomic, assign) CGFloat animationProgress;
@property (nonatomic, assign) CGFloat animationSpeed;
...
- (void)play;
- (void)pause; 
...

LOTAnimationView所有的加载方法,最终执行的都是把JSON字典传到LOTComposition类中,组装LOTComposition对象,当然还会有一些缓存获取,值判断等的逻辑,但是核心就是产生一个LOTComposition对象:

+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
  ...
  if (JSONObject && !error) {
    LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:bundle];
    [[LOTAnimationCache sharedCache] addAnimation:laScene forKey:animationName];
    laScene.cacheKey = animationName;
    return laScene;
  }
  NSLog(@"%s: Animation Not Found", __PRETTY_FUNCTION__);
  return nil;
}

2)LOTComposition类用来解析整个动画的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;
  }
  
  _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];
  }
  
  NSArray *layersJSON = jsonDictionary[@"layers"];
  if (layersJSON) {
    _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
                                            withAssetGroup:_assetGroup
                                             withFramerate:_framerate];
  }
  
  [_assetGroup finalizeInitializationWithFramerate:_framerate];
}

在对JSON字典的解析过程中,会拆分成几种不同的信息,包括:整体关键帧信息、所需图片资源信息、所有子layer的信息。并将图片组和layer组分别传入到LOTAssetGroup和LOTLayerGroup中做进一步处理。

3)LOTLayerGroup类用于解析JSON中“layers”层的数据,并将单独的layer数据传递给LOTLayer处理。核心代码如下:

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

4)接下来进入LOTLayer类中,这里最终把json文件中单个layer对应的数据映射出来。

...
@property (nonatomic, readonly) NSString *layerName;
@property (nonatomic, readonly, nullable) NSString *referenceID;
@property (nonatomic, readonly) NSNumber *layerID;
@property (nonatomic, readonly) LOTLayerType layerType;
@property (nonatomic, readonly, nullable) NSNumber *parentID;
@property (nonatomic, readonly) NSNumber *startFrame;
@property (nonatomic, readonly) NSNumber *inFrame;
@property (nonatomic, readonly) NSNumber *outFrame;
@property (nonatomic, readonly) NSNumber *timeStretch;
@property (nonatomic, readonly) CGRect layerBounds;
@property (nonatomic, readonly, nullable) NSArray<LOTShapeGroup *> *shapes;
@property (nonatomic, readonly, nullable) NSArray<LOTMask *> *masks;
...

以上属性和json文件对应的key有一一对应关系,比如layerName对应json文件中的nm,layerType对应ty等等,每个layer中包含Layer所需的基本信息,transform变化需要的则是每个LOTKeyframeGroup 类型的属性。这里面包含了该Layer 的 transform变化的关键帧数组,而masks 和 shapes 的信息包含在上面的两个同名数组中。

5)前面四步,已经把动画需要的数据全部准备好了,接下来就需要进行动画显示。

最底层的LOTLayerContainer继承自CALayer,添加了currentFrame属性,LOTCompositionContainer又是继承自LOTLayerContainer,为LOTCompositionContainer对象添加了一个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];
  }
  if (_transformInterpolator && [_transformInterpolator hasUpdateForFrame:newFrame]) {
    _wrapperLayer.transform = [_transformInterpolator transformForFrame:newFrame];
  }
  [_contentsGroup updateWithFrame:newFrame withModifierBlock:nil forceLocalUpdate:forceUpdate];
  _maskLayer.currentFrame = newFrame;
}

它实际上完成了以下几件事:
1.根据子Layer的起始帧和结束帧判断当前帧子Layer是否显示
2.更新子Layer当前帧的透明度
3.更新子Layer当前帧的transform
4.更新子Layer中路径和形状等内容的变化

6)上面动画显示的2,3,4步都是通过XXInterpolator这些类,来从当前frame中计算出我们需要的值,我们以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;
...

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

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

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

总结:

Lottie提供了多种便利的方式,供我们加载酷炫的动画,对用户体验有极大的提升。对使用者来说,只需要引入包含动效的json文件和资源文件,调用lottie提供的属性和api完成动画绘制。Lottie内部帮我们做了json文件映射到不同类的不同属性中,通过一系列的计算,确定出每一帧的数据,然后完美的显示在屏幕上,这样的神器,以后要多多用起来啦!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Lottie动画使用及原理分析 的相关文章

随机推荐

  • android mediaplay 出现IllegalStateException的几种可能性及解决办法

    1 错误log java lang IllegalStateException at android media MediaPlayer setDataSource Native Method at android media MediaP
  • 创建.xml的矢量图片;使用Android studio 和 SVG图 生成.xml矢量图标

    Android开发中 xff0c 为什么要使用矢量图标 xff1f 使用矢量图标有什么好处 xff1f 如果使用 png xff1b jpg 这样的图片 xff0c 一般在资源文件中 xff0c 都需要准备不同分辨率的图 这样既让apk臃肿
  • 设计模式-单例模式

    本文章参考慕课DocMike老师的讲解 xff0c 作为个人笔记 xff0c 也希望能帮到需要的人 1 单例模式 单例模式 xff08 Singleton Pattern xff09 是 Java 中最简单的设计模式之一 这种类型的设计模式
  • Android studio 3 gradle配置问题

    目录 问题描述原因解决方法1 xff09 使用低版本的三方依赖库2 xff09 手动声明 xff0c 排除高版本的依赖参考文章 问题描述 Duplicate class android support design widget Coord
  • 51单片机定时器中断按键消抖(无延时)

    单片机入门学习记录 xff08 二 xff09 在机械按键的触点闭合和断开时 xff0c 都会产生抖动 xff0c 为了保证系统能正确识别按键的开关 xff0c 就必须对按键的抖动进行处理 按键的抖动对于人类来说是感觉不到的 xff0c 但
  • Ubuntu常用命令

    目录 更新仓库命令查看软件依赖包安装软件定时查看某个命令查找文件查找文件中的内容 grep 将命令行中输出内容保存文档scp通过ssh连接复制文件修改环境变量删除指定路径下包含某个关键字的文件与文件夹压缩解压查看运行信息远程桌面连接Wind
  • C#: WMI 获取远程 Windows 主机信息

    起步文档 xff1a WMI 基本介绍 WMI调用基本步骤 一个简单的远程访问例子 xff1a xff08 参考自MSDN How To Connect to a Remote Computer xff09 span class hljs
  • 端到端是什么意思?

    不久前 xff0c 燕姐 表扬了我 原话是 xff1a 像你这样端到端负责的人现在越来越少了 哈哈 xff0c 听到这话 xff0c 还是有点高兴的 xff0c 今天我来闲扯一下端到端 客户需要一个求立方差的系统 假设是fun系统 xff1
  • 电磁波和声波对比实验

    如图 xff0c 电话拨通 xff0c 能听到两个手机的声音 不断对右边的罩子进行抽气 xff0c 右边手机的声音越来越小 抽成真空的时候 xff0c 右边手机的声音消失 xff0c 但左边手机仍然如初 此时 xff0c 右边手机发送的信号
  • eclipse用MVC模式编写简单的JSP登录界面(一)

    刚开始接触JSP xff0c 打算写写博客记录记录 xff0c 大佬可以不用看了 1 JSP 在编写登录界面之前需要安装服务器 xff08 这里使用的是Tomcat xff09 并且安装IDE以及进行相关的部署 这里就不进行赘述了 xff0
  • seata

    Seata 1 seata概述 1 1 Seata简介 Seata 是一款开源的分布式事务解决方案 xff0c 致力于提供高性能和简单易用的分布式事务服务 Seata 将为用户提供了 AT TCC SAGA 和 XA 事务模式 xff0c
  • git clone出现fatal: HTTP request failed --git版本问题

    当git版本低于2 0版本时 xff0c 在push或clone代码时容易出现 fatal HTTP request failed 的问题 当前 xff0c git的最新版本是2 33 1 但是 xff0c 当我按官网提示 xff0c 用
  • 层次狄利克雷过程HDP(Hierarchical Dirichlet Processes)

    HDP本质是一个聚类算法 xff0c 自动决定聚类的个数 HDP HMM也是一个聚类算法 xff0c 自动决定HMM的隐状态的个数 xff0c 以每个隐状态作为一个聚类 LDA是主题模型 xff0c 可以被用作聚类算法 HDP也是个主题模型
  • vscode离线安装插件方法

    在实际工作中 xff0c 由于大多开发环境为内网开发 xff0c 无法连接外网 xff0c 需要进行离线安装相应插件 xff0c 此文用于记录vscode离线安装插件方法 1 方法一 xff1a 到vscode官网 https market
  • AD--------简单规则的设定

    这学期打了好多块板子 xff0c 都是在大佬的帮助下弄得 xff0c 嘿嘿嘿 xff0c 以后得多多练习 AD的规则设定 xff0c 反正对于英文不好的我来说还是比较难得 xff0c 但是现在画的板子规则设定都比较简单 rules 最小间距
  • linux系统编程中的信号量--模拟生产者与消费者

    FileName producer and customer c description This app demonstrates how to use the semaphore solve the problem about the
  • MySQL数据库索引相关知识

    目录 定义重点 存储原理B TreeB 43 TreeMyISAMInnoDB主键使用自增整形主键联合索引 原则那些情况应当创建索引不适合见索引 定义 索引时帮助MySQL高效获取数据的数据结构 简单说 xff1a 排好序的快速查找数据结构
  • 解决Mac M1环境下使用Goland debug失败的问题

    问题描述 xff1a 在m1环境下 xff0c 使用GoLand工具 xff0c 项目可以正常Run xff0c 但无法Debug运行 error could not launch process can not run under Ros
  • 解决“java.sql.SQLException: Expression #1 of ORDER BY clause is not in SELECT list,references column”

    在一次跑项目的时候 xff0c 报了这个错 分析原因 xff1a 百度发现是Mysql5 7及以上版本默认将 sql mode 的 ONLY FULL GROUP BY 模式设置为打开状态 解决办法 xff1a 1 将数据库换回5 6及以下
  • Lottie动画使用及原理分析

    1 Lottie是什么 xff1f Lottie是Airbnb开源的一个动画渲染库 xff0c 支持多平台 xff0c 包括iOS Android React Native以及Flutter xff08 https github com a