Lottie主要类图:

Lottie对外通过控件LottieAnimationView暴露接口,控制动画。

LottieAnimationView继承自ImageView,通过当前时间绘制canvas显示到界面上。这里有两个关键类:LottieComposition 负责解析json描述文件,把json内容转成Java数据对象;LottieDrawable负责绘制,把LottieComposition转成的数据对象绘制成drawable显示到View上。顺序如下:

json文件解析

LottieComposition负责解析json文件,建立数据到java对象的映射关系。

解析json外部结

LottieComposition封装整个动画的信息,包括动画大小,动画时长,帧率,用到的图片,字体,图层等等。

json外部结构

{"v": "5.1.13",       // bodymovin 版本"fr": 30,            // 帧率"ip": 0,             // 起始关键帧"op": 20,            // 结束关键帧"w": 150,            // 视图宽"h": 130,            // 视图高"nm": "鹅头收起动画",  // 名称"ddd": 0,             // 3d"assets": [],        // 资源集合 "layers": [],        // 图层集合"masker": []         // 蒙层集合
}

上图为一个动画json文件,上面给出了各个参数的含义。其中ip表示其实关键帧,一般为0,op表示动画的结束关键帧,fr表示帧率,所以动画时间等于:(op-ip)/fr 。w和h分别表示视图的宽和高。

由于assets、layers、masker里面的数据可能很大,所以上面用空数组代替。其中layers是一个图层集合,它里面数据一般很大,里面包含了当前动画的所有图层数据,assets是一个资源集合,它里面包含了当前动画使用的资源图层数据。masks则表示蒙层集合,里面包含了所有的蒙层数据。

在lottie-android中,处理以上这些数据的代码如下所示(删除了一些相关性不强的代码,完整的代码请看lottie-android源码):

public static LottieComposition parse(JsonReader reader) throws IOException {float scale = Utils.dpScale();float startFrame = 0f;float endFrame = 0f;float frameRate = 0f;final LongSparseArray<Layer> layerMap = new LongSparseArray<>();final List<Layer> layers = new ArrayList<>();int width = 0;int height = 0;Map<String, List<Layer>> precomps = new HashMap<>();Map<String, LottieImageAsset> images = new HashMap<>();Map<String, Font> fonts = new HashMap<>();List<Marker> markers = new ArrayList<>();SparseArrayCompat<FontCharacter> characters = new SparseArrayCompat<>();LottieComposition composition = new LottieComposition();reader.beginObject();while (reader.hasNext()) {switch (reader.nextName()) {case "w":width = reader.nextInt();break;case "h":height = reader.nextInt();break;case "ip":startFrame = (float) reader.nextDouble();break;case "op":endFrame = (float) reader.nextDouble() - 0.01f;break;case "fr":frameRate = (float) reader.nextDouble();break;case "v":String version = reader.nextString();String[] versions = version.split("\\.");int majorVersion = Integer.parseInt(versions[0]);int minorVersion = Integer.parseInt(versions[1]);int patchVersion = Integer.parseInt(versions[2]);if (!Utils.isAtLeastVersion(majorVersion, minorVersion, patchVersion,4, 4, 0)) {composition.addWarning("Lottie only supports bodymovin >= 4.4.0");}break;case "layers":parseLayers(reader, composition, layers, layerMap);break;case "assets":parseAssets(reader, composition, precomps, images);break;case "fonts":parseFonts(reader, fonts);             //解析字体break;case "chars":parseChars(reader, composition, characters);  //解析字符break;case "markers":                 //解析蒙层parseMarkers(reader, composition, markers);break;default:reader.skipValue();}}reader.endObject();int scaledWidth = (int) (width * scale);int scaledHeight = (int) (height * scale);Rect bounds = new Rect(0, 0, scaledWidth, scaledHeight);composition.init(bounds, startFrame, endFrame, frameRate, layers, layerMap, precomps,images, characters, fonts, markers);return composition;}

图层元素 layer

动画是由一个一个的图层组合起来,并在图层上进行偏移、缩放等操作来实现动画的。图层的解析是lottie的主要功能模块。 一个layer图层的数据格式一般如下:

{"ddd": 0,          // 是否为3d"ind": 1,          // layer的ID,唯一"ty": 0,           // 图层类型"nm": "鹅头收起",   // 图层名称"refId": "comp_0", // 引用的资源,图片/预合成层"sr": 1,"ks": {},          // 变换。对应AE中的变换设置layer: [],         // 该图层包含的子图层shaps: [],         // 形状图层"ao": 0,"w": 1334,"h": 750,"ip": 0,          // 该图层开始关键帧"op": 60,         // 该图层结束关键帧"st": 0,          // 该图层"bm": 0
}

上面是一个layer图层的object的格式。

其中说明一下nm属性,该属性是在AE中对该图层的命名,通过在SVG中修改该命名,可以设置对应的svg的class和id。如果命名为'#svgId',生成的对应的svg元素的id则为'svgId';如果命名为'.svg-class',则生成的对应的svg元素的class为'svg-class'。

ty表示类型,例如:

  • 2: image,图片
  • 0: comp,合成图层
  • 1: solid;
  • 3: null;
  • 4: shape,形状图层
  • 5: text,文字

lottie-android中对layers图层数据相应的处理有:

public static Layer parse(JsonReader reader, LottieComposition composition) throws IOException {// This should always be set by After Effects. However, if somebody wants to minify// and optimize their json, the name isn't critical for most cases so it can be removed.String layerName = "UNSET";Layer.LayerType layerType = null;String refId = null;long layerId = 0;int solidWidth = 0;int solidHeight = 0;int solidColor = 0;int preCompWidth = 0;int preCompHeight = 0;long parentId = -1;float timeStretch = 1f;float startFrame = 0f;float inFrame = 0f;float outFrame = 0f;String cl = null;boolean hidden = false;Layer.MatteType matteType = Layer.MatteType.NONE;AnimatableTransform transform = null;AnimatableTextFrame text = null;AnimatableTextProperties textProperties = null;AnimatableFloatValue timeRemapping = null;List<Mask> masks = new ArrayList<>();List<ContentModel> shapes = new ArrayList<>();reader.beginObject();while (reader.hasNext()) {switch (reader.nextName()) {case "nm":layerName = reader.nextString();break;case "ind":layerId = reader.nextInt();break;case "refId":refId = reader.nextString();break;case "ty":int layerTypeInt = reader.nextInt();if (layerTypeInt < Layer.LayerType.UNKNOWN.ordinal()) {layerType = Layer.LayerType.values()[layerTypeInt];} else {layerType = Layer.LayerType.UNKNOWN;}break;case "parent":parentId = reader.nextInt();break;case "sw":solidWidth = (int) (reader.nextInt() * Utils.dpScale());break;case "sh":solidHeight = (int) (reader.nextInt() * Utils.dpScale());break;case "sc":solidColor = Color.parseColor(reader.nextString());break;case "ks":           //ks变换transform = AnimatableTransformParser.parse(reader, composition);break;case "tt":matteType = Layer.MatteType.values()[reader.nextInt()];composition.incrementMatteOrMaskCount(1);break;case "masksProperties":reader.beginArray();while (reader.hasNext()) {masks.add(MaskParser.parse(reader, composition));}composition.incrementMatteOrMaskCount(masks.size());reader.endArray();break;case "shapes":reader.beginArray();while (reader.hasNext()) {ContentModel shape = ContentModelParser.parse(reader, composition);if (shape != null) {shapes.add(shape);}}reader.endArray();break;case "t":reader.beginObject();while (reader.hasNext()) {switch (reader.nextName()) {case "d":text = AnimatableValueParser.parseDocumentData(reader, composition);break;case "a":reader.beginArray();if (reader.hasNext()) {textProperties = AnimatableTextPropertiesParser.parse(reader, composition);}while (reader.hasNext()) {reader.skipValue();}reader.endArray();break;default:reader.skipValue();}}reader.endObject();break;case "ef":reader.beginArray();List<String> effectNames = new ArrayList<>();while (reader.hasNext()) {reader.beginObject();while (reader.hasNext()) {switch (reader.nextName()) {case "nm":effectNames.add(reader.nextString());break;default:reader.skipValue();}}reader.endObject();}reader.endArray();composition.addWarning("Lottie doesn't support layer effects. If you are using them for " +" fills, strokes, trim paths etc. then try adding them directly as contents " +" in your shape. Found: " + effectNames);break;case "sr":timeStretch = (float) reader.nextDouble();break;case "st":startFrame = (float) reader.nextDouble();break;case "w":preCompWidth = (int) (reader.nextInt() * Utils.dpScale());break;case "h":preCompHeight = (int) (reader.nextInt() * Utils.dpScale());break;case "ip":inFrame = (float) reader.nextDouble();break;case "op":outFrame = (float) reader.nextDouble();break;case "tm":timeRemapping = AnimatableValueParser.parseFloat(reader, composition, false);break;case "cl":cl = reader.nextString();break;case "hd":hidden = reader.nextBoolean();break;default:reader.skipValue();}}reader.endObject();// Bodymovin pre-scales the in frame and out frame by the time stretch. However, that will// cause the stretch to be double counted since the in out animation gets treated the same// as all other animations and will have stretch applied to it again.inFrame /= timeStretch;outFrame /= timeStretch;List<Keyframe<Float>> inOutKeyframes = new ArrayList<>();// Before the in frameif (inFrame > 0) {Keyframe<Float> preKeyframe = new Keyframe<>(composition, 0f, 0f, null, 0f, inFrame);inOutKeyframes.add(preKeyframe);}// The + 1 is because the animation should be visible on the out frame itself.outFrame = (outFrame > 0 ? outFrame : composition.getEndFrame());Keyframe<Float> visibleKeyframe =new Keyframe<>(composition, 1f, 1f, null, inFrame, outFrame);inOutKeyframes.add(visibleKeyframe);Keyframe<Float> outKeyframe = new Keyframe<>(composition, 0f, 0f, null, outFrame, Float.MAX_VALUE);inOutKeyframes.add(outKeyframe);if (layerName.endsWith(".ai") || "ai".equals(cl)) {composition.addWarning("Convert your Illustrator layers to shape layers.");}return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId,masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startFrame,preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType,timeRemapping, hidden);}
}

ks变换

ks对应AE中图层的变换属性,可以通过设置锚点、位置、旋转、缩放、透明度等来控制图层,并设置这些属性的变换曲线,来实现动画。下面是一个ks属性值:

"ks": { // 变换。对应AE中的变换设置"o": { // 透明度"a": 0,"k": 100,"ix": 11},"r": { // 旋转"a": 0,"k": 0,"ix": 10},"p": { // 位置"a": 0,"k": [-167, 358.125, 0],"ix": 2},"a": { // 锚点"a": 0,"k": [667, 375, 0],"ix": 1},"s": { // 缩放"a": 0,"k": [100, 100, 100],"ix": 6}
}

lottie-android会把ks处理成transform的属性,用于对元素进行变换操作。transform包含了translate(平移)、scale(缩放)、rotate(旋转)、skew(倾斜)等几种。lottie-android中处理ks(变换)的相关代码为:

 public static AnimatableTransform parse(JsonReader reader, LottieComposition composition) throws IOException {AnimatablePathValue anchorPoint = null;AnimatableValue<PointF, PointF> position = null;AnimatableScaleValue scale = null;AnimatableFloatValue rotation = null;AnimatableIntegerValue opacity = null;AnimatableFloatValue startOpacity = null;AnimatableFloatValue endOpacity = null;AnimatableFloatValue skew = null;AnimatableFloatValue skewAngle = null;boolean isObject = reader.peek() == JsonToken.BEGIN_OBJECT;if (isObject) {reader.beginObject();}while (reader.hasNext()) {switch (reader.nextName()) {case "a":reader.beginObject();while (reader.hasNext()) {if (reader.nextName().equals("k")) {anchorPoint = AnimatablePathValueParser.parse(reader, composition);} else {reader.skipValue();}}reader.endObject();break;case "p":position =AnimatablePathValueParser.parseSplitPath(reader, composition);break;case "s":scale = AnimatableValueParser.parseScale(reader, composition);break;case "rz":composition.addWarning("Lottie doesn't support 3D layers.");case "r":/*** Sometimes split path rotation gets exported like:*         "rz": {*           "a": 1,*           "k": [*             {}*           ]*         },* which doesn't parse to a real keyframe.*/rotation = AnimatableValueParser.parseFloat(reader, composition, false);if (rotation.getKeyframes().isEmpty()) {rotation.getKeyframes().add(new Keyframe(composition, 0f, 0f, null, 0f, composition.getEndFrame()));} else if (rotation.getKeyframes().get(0).startValue == null) {rotation.getKeyframes().set(0, new Keyframe(composition, 0f, 0f, null, 0f, composition.getEndFrame()));}break;case "o":opacity = AnimatableValueParser.parseInteger(reader, composition);break;case "so":startOpacity = AnimatableValueParser.parseFloat(reader, composition, false);break;case "eo":endOpacity = AnimatableValueParser.parseFloat(reader, composition, false);break;case "sk":skew = AnimatableValueParser.parseFloat(reader, composition, false);break;case "sa":skewAngle = AnimatableValueParser.parseFloat(reader, composition, false);break;default:reader.skipValue();}}if (isObject) {reader.endObject();}if (isAnchorPointIdentity(anchorPoint)) {anchorPoint = null;}if (isPositionIdentity(position)) {position = null;}if (isRotationIdentity(rotation)) {rotation = null;}if (isScaleIdentity(scale)) {scale = null;}if (isSkewIdentity(skew)) {skew = null;}if (isSkewAngleIdentity(skewAngle)) {skewAngle = null;}return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity, startOpacity, endOpacity, skew, skewAngle);}

shape

shape参数的值,对应AE中图层的内容中的形状设置的内容,其主要用于绘制图形。下面一个shape的json为例:

"shapes": [{"ty": "gr", // 类型。混合图层"it": [{ // 各图层json"ind": 0,"ty": "sh", // 类型,sh表示图形路径"ix": 1,"ks": {"a": 0,"k": {"i": [ // 内切线点集合[0, 0],[0, 0]],"o": [ // 外切线点集合[0, 0],[0, 0]],"v": [ // 顶点坐标集合[182, -321.75],[206.25, -321.75]], "c": false // 贝塞尔路径闭合},"ix": 2},"nm": "路径 1","mn": "ADBE Vector Shape - Group","hd": false},{"ty": "st", // 类型。图形描边"c": { // 线的颜色"a": 0,"k": [0, 0, 0, 1],"ix": 3},"o": { // 线的不透明度"a": 0,"k": 100,"ix": 4},"w": { // 线的宽度"a": 0,"k": 3,"ix": 5},"lc": 2, // 线段的头尾样式"lj": 1, // 线段的连接样式"ml": 4, // 尖角限制"nm": "描边 1","mn": "ADBE Vector Graphic - Stroke","hd": false}]
}]

上面是一个shape形状的json示例,可以看出不同的shape类型,参数也不同。shape对应的是AE中的图层的内容的设置。shape中的ty字段表示shape的类型,ty有以下几种:

  • gr: 图形合并
  • st: 图形描边
  • fl: 图形填充
  • tr: 图形变换
  • sh: 图形路径
  • el: 椭圆路径
  • rc: 矩形路径
  • tm: 剪裁路径

lottie-android中处理shape的相关代码为:

static ContentModel parse(JsonReader reader, LottieComposition composition)throws IOException {String type = null;reader.beginObject();// Unfortunately, for an ellipse, d is before "ty" which means that it will get parsed// before we are in the ellipse parser.// "d" is 2 for normal and 3 for reversed.int d = 2;typeLoop:while (reader.hasNext()) {switch (reader.nextName()) {case "ty":type = reader.nextString();break typeLoop;case "d":d = reader.nextInt();break;default:reader.skipValue();}}if (type == null) {return null;}ContentModel model = null;switch (type) {case "gr":model = ShapeGroupParser.parse(reader, composition);break;case "st":model = ShapeStrokeParser.parse(reader, composition);break;case "gs":model = GradientStrokeParser.parse(reader, composition);break;case "fl":model = ShapeFillParser.parse(reader, composition);break;case "gf":model = GradientFillParser.parse(reader, composition);break;case "tr":model = AnimatableTransformParser.parse(reader, composition);break;case "sh":model = ShapePathParser.parse(reader, composition);break;case "el":model = CircleShapeParser.parse(reader, composition, d);break;case "rc":model = RectangleShapeParser.parse(reader, composition);break;case "tm":model = ShapeTrimPathParser.parse(reader, composition);break;case "sr":model = PolystarShapeParser.parse(reader, composition);break;case "mm":model = MergePathsParser.parse(reader);composition.addWarning("Animation contains merge paths. Merge paths are only " +"supported on KitKat+ and must be manually enabled by calling " +"enableMergePathsForKitKatAndAbove().");break;case "rp":model = RepeaterParser.parse(reader, composition);break;default:Log.w(L.TAG, "Unknown shape type " + type);}while (reader.hasNext()) {reader.skipValue();}reader.endObject();return model;}

Lottie—json文件解析相关推荐

  1. Json文件解析(下

    Json文件解析(下) 代码地址:https://github.com/nlohmann/json 从STL容器转换 任何序列容器(std::array,std::vector,std::deque, ...

  2. Json文件解析(上)

    Json文件解析(上) 代码地址:https://github.com/nlohmann/json 自述文件 alt=GitHub赞助商 data-canonical-src="https: ...

  3. PHP JSON文件解析并获取key、value,判断key是否存在

    /******************************************************************************* PHP JSON文件解析并获取key. ...

  4. Golang Json文件解析为结构体工具-json2go

    代码地址如下: http://www.demodashi.com/demo/14946.html 概述 json2go是一个基于Golang开发的轻量json文件解析.转换命令行工具,目前支持转换输出 ...

  5. TCP多人聊天室实现 JSON文件解析

    TCP多人聊天室实现 JSON文件解析 1. TCP多人聊天室实现 1.1 分析 客户端功能:1. 数据发送2. 数据接收技术:1. socket2. 输入流和输出流3. 多线程,客户端功能模块有两个 ...

  6. 【JSON文件解析】JSON文件

    文章目录 概要:本期主要介绍Qt解析JSON数据格式文件的方式. 一.JSON数据格式 1.JSON类似于XML,在JSON文件中,==有且只有一个根节点 2.JSON有两种主流包含型构造字符:{对象 ...

  7. Laravel5.2目录结构及composer.json文件解析

    目录或文件说明 |– app包含Controller.Model.路由等在内的应用目录,大部分业务将在该目录下进行 | |– Console命令行程序目录 | | |– Commands包含了用于命令 ...

  8. json文件解析工具_JSON格式的文本文件,怎么解析不成功?

    小勤:上次那个JSON数据是复制到Excel的一个单元格里的,在PQ里直接解析就可以了,但一般JSON数据都是放在一个文本文件里的,怎么解析不成功?你看: Step-01:从文本文件 Step-02: ...

  9. 安卓大json文件解析_安卓解析 json 4种格式 全解析

    1  简单的一个  { ,,,,} 2  里面有数组 { , [{,,},{,,}],} 3 直接一个数组 4 数组里有数组 5 其他情况? 没有吧,除非json格式不规范 主代码: package ...

最新文章

  1. myeclipse按.自动提示方法
  2. 外星人入侵 python 飞船位置_《python从入门到实践》项目一:外星人入侵
  3. 空间谱专题10:MUSIC算法
  4. mysql最左_Mysql最左原则
  5. mysql数据库中的校对集
  6. php 发送post请求json,thinkphp ,php post发送json请求,就收post请求
  7. 手也很光滑的飞鸽传书
  8. Netty工作笔记0044---scheduledTaskQueue
  9. python数字替换成中文replace_Python3字符串替换replace(),translate(),re.sub()
  10. 03-肯德基点餐:抽象工厂模式
  11. List 按照中文姓名升序排列
  12. 北京林业大学计算机辅助设计,计算机辅助风景园林规划设计策略研究
  13. 银行管理系统实例(C语言版)
  14. 单片机语音播报怎么做?语音模块原理及程序编写思路
  15. 20.5 Shell脚本中的逻辑判断;20.6 文件目录属性判断;20.7 if特殊用法;20.8 20.9 cace判断(上下)...
  16. 中国余数定理解题步骤
  17. 最新接单抢单系统返利+资金盘+区块链自动抢单系统源码
  18. 使用计算机进行飞机设计属于,利用计算机对飞机、汽车、机械、服装等进行设计、绘图属于()。...
  19. intel-Altera design template installation failed
  20. 智能一代云平台(十二):轮次设置

热门文章

  1. html字典模板,在flas中从html模板将字典保存到数据库
  2. 量子计算求解函数因子:Bernstein-Vazirani Algorithm
  3. 解决问题,真的是职场最重要的能力么?
  4. java文本文件加密解密类
  5. 如何升级XP到SP3
  6. Windows本地安装部署easymock
  7. oracle 查询教师所有的单位即不重复的Depart列
  8. 链接的时候如何去掉没有用到的函数、目标文件
  9. VSCode搭建Go开发环境(2020-04-13更新)
  10. 百度副总裁李硕:AI能深入场景创造真价值,从传感器到大屏仅是数字化开始...