catalogue
- What can Lottie do
- Lottie animation uses the call process
- Introduction to Json field
- Resolve to lottieposition
- data
- summary
1, What can Lottie do
In terms of realizing animation, the development cost of the native way is relatively high. airbnb's open-source lottie is supported by Android, iOS, RN and other versions. After designing the animation through AE, the designer exports json and material files through AE plug-in Bodymovin.

Yes https://lottiefiles.com/popular Look at some popular Lottie dynamics

The client can achieve the corresponding animation effect by loading, parsing, rendering and playing. This idea and design is very worthy of learning and reference. We will study and analyze the next two articles. Welcome to discuss and exchange.
2, Lottie animation uses the call process
Lottie's animation call process
1. establish LottieAnimationView 2. establish LottieDrawable 3. analysis json File as LottieComposition object 4. lottieDrawable.setComposition(lottieComposition) lottieAnimationView.setImageDrawable(lottieDrawable) 5. Play animation playAnimation() stay onValueChanged Each created Drawable Redrawing will be carried out as required to achieve the effect of animation.
It is very simple for developers to use. You can refer to the use of sample in lottie source code.
3, Introduction to Json field
We use the Android wave in the demo provided by Lottie Android JSON and anima JSON as an example to learn object

Let's look at the fields in assets Object - > assets: resources

You can see that there are layers in object s and possibly in assets. What information does the layer field over there contain. Object - > layers: layers

Object - > layers - > TY: layer type

Object - > layers - > KS: layer transformation

The fields in "o, r, p, a and s" are the same. They are all a, k, X and ix. A and k are described below. x. I don't know what it's for. object->layers->ks-> o ,r, p, a, s

The fields of Lottie json are basically explained. Let's take a look at the process of parsing into Lottie position in combination with the source code of Lottie Android
4, Resolve to lottieposition
json parser, parsed as a lottieposition object

4.1 lottieposition parsing process
Lottieposition object

json parser, parsed as a lottieposition object
//The parser of lottie json is parsed into lottieposition object public class LottieCompositionMoshiParser { private static final JsonReader.Options NAMES = JsonReader.Options.of( "w", // 0 "h", // 1 "ip", // 2 "op", // 3 "fr", // 4 "v", // 5 "layers", // 6 "assets", // 7 "fonts", // 8 "chars", // 9 "markers" // 10 ); 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.selectName(NAMES)) { case 0: width = reader.nextInt(); break; case 1: height = reader.nextInt(); break; case 2: startFrame = (float) reader.nextDouble(); break; case 3: endFrame = (float) reader.nextDouble() - 0.01f; break; case 4: frameRate = (float) reader.nextDouble(); break; case 5: 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 6://Focus on the implementation of parseLayers parseLayers(reader, composition, layers, layerMap); break; case 7: parseAssets(reader, composition, precomps, images); break; case 8: parseFonts(reader, fonts); break; case 9: parseChars(reader, composition, characters); break; case 10: parseMarkers(reader, markers); break; default: reader.skipName(); reader.skipValue(); } } // w. h automatically adapts according to the pixel density int scaledWidth = (int) (width * scale); int scaledHeight = (int) (height * scale); Rect bounds = new Rect(0, 0, scaledWidth, scaledHeight); //After the json content is parsed, the composition init is called to assign the LottieComposition field to complete the work of json to LottieComposition. composition.init(bounds, startFrame, endFrame, frameRate, layers, layerMap, precomps, images, characters, fonts, markers); return composition; } private static void parseLayers(JsonReader reader, LottieComposition composition, List<Layer> layers, LongSparseArray<Layer> layerMap) throws IOException { int imageCount = 0; reader.beginArray(); //Traverse and parse each layer, while (reader.hasNext()) { //Layer resolution Layer layer = LayerParser.parse(reader, composition); if (layer.getLayerType() == Layer.LayerType.IMAGE) { imageCount++; } //Adding layers and map s to improve efficiency layers.add(layer); layerMap.put(layer.getId(), layer); } reader.endArray(); }
4.2 analysis process of layers layer
Resolved as a Layer object

Parse layers in json into Layer objects through LayerParser
//Parse layers in json into Layer objects through LayerParser public class LayerParser { //Corresponding to the fields of layers in json private static final JsonReader.Options NAMES = JsonReader.Options.of( "nm", // 0 "ind", // 1 "refId", // 2 "ty", // 3 "parent", // 4 "sw", // 5 "sh", // 6 "sc", // 7 "ks", // 8 "tt", // 9 "masksProperties", // 10 "shapes", // 11 "t", // 12 "ef", // 13 "sr", // 14 "st", // 15 "w", // 16 "h", // 17 "ip", // 18 "op", // 19 "tm", // 20 "cl", // 21 "hd" // 22 ); private static final JsonReader.Options TEXT_NAMES = JsonReader.Options.of( "d", "a" ); private static final JsonReader.Options EFFECTS_NAMES = JsonReader.Options.of( "ty", "nm" ); 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; BlurEffect blurEffect = null; DropShadowEffect dropShadowEffect = null; 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.selectName(NAMES)) { case 0: layerName = reader.nextString(); break; case 1: layerId = reader.nextInt(); break; case 2: refId = reader.nextString(); break; case 3: int layerTypeInt = reader.nextInt(); //Look at layer The value of layertype corresponds to ty in json if (layerTypeInt < Layer.LayerType.UNKNOWN.ordinal()) { layerType = Layer.LayerType.values()[layerTypeInt]; } else { layerType = Layer.LayerType.UNKNOWN; } break; case 4: parentId = reader.nextInt(); break; case 5: solidWidth = (int) (reader.nextInt() * Utils.dpScale()); break; case 6: solidHeight = (int) (reader.nextInt() * Utils.dpScale()); break; case 7: solidColor = Color.parseColor(reader.nextString()); break; case 8: //Layer transformation, the focus of animation transform = AnimatableTransformParser.parse(reader, composition); break; case 9: int matteTypeIndex = reader.nextInt(); if (matteTypeIndex >= Layer.MatteType.values().length) { composition.addWarning("Unsupported matte type: " + matteTypeIndex); break; } matteType = Layer.MatteType.values()[matteTypeIndex]; switch (matteType) { case LUMA: composition.addWarning("Unsupported matte type: Luma"); break; case LUMA_INVERTED: composition.addWarning("Unsupported matte type: Luma Inverted"); break; } composition.incrementMatteOrMaskCount(1); break; case 10: //Mask analysis reader.beginArray(); while (reader.hasNext()) { masks.add(MaskParser.parse(reader, composition)); } composition.incrementMatteOrMaskCount(masks.size()); reader.endArray(); break; case 11: reader.beginArray(); while (reader.hasNext()) { ContentModel shape = ContentModelParser.parse(reader, composition); if (shape != null) { shapes.add(shape); } } reader.endArray(); break; case 12: reader.beginObject(); while (reader.hasNext()) { switch (reader.selectName(TEXT_NAMES)) { case 0: text = AnimatableValueParser.parseDocumentData(reader, composition); break; case 1: reader.beginArray(); if (reader.hasNext()) { textProperties = AnimatableTextPropertiesParser.parse(reader, composition); } while (reader.hasNext()) { reader.skipValue(); } reader.endArray(); break; default: reader.skipName(); reader.skipValue(); } } reader.endObject(); break; case 13: //Special effects reader.beginArray(); List<String> effectNames = new ArrayList<>(); while (reader.hasNext()) { reader.beginObject(); while (reader.hasNext()) { switch (reader.selectName(EFFECTS_NAMES)) { case 0: int type = reader.nextInt(); if (type == 29) { blurEffect = BlurEffectParser.parse(reader, composition); } else if (type == 25) { dropShadowEffect = new DropShadowEffectParser().parse(reader, composition); } break; case 1: String effectName = reader.nextString(); effectNames.add(effectName); break; default: reader.skipName(); 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 14: timeStretch = (float) reader.nextDouble(); break; case 15: startFrame = (float) reader.nextDouble(); break; case 16: preCompWidth = (int) (reader.nextInt() * Utils.dpScale()); break; case 17: preCompHeight = (int) (reader.nextInt() * Utils.dpScale()); break; case 18: inFrame = (float) reader.nextDouble(); break; case 19: outFrame = (float) reader.nextDouble(); break; case 20: timeRemapping = AnimatableValueParser.parseFloat(reader, composition, false); break; case 21: cl = reader.nextString(); break; case 22: hidden = reader.nextBoolean(); break; default: reader.skipName(); reader.skipValue(); } } reader.endObject(); List<Keyframe<Float>> inOutKeyframes = new ArrayList<>(); // Before the in frame if (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."); } //Generate the corresponding Layer object according to the value parsed above. This object is for drawing 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, blurEffect, dropShadowEffect); } }
4.3 analysis process of KS layer transformation
Is resolved to an object AnimatableTransform

Parse ks in json into animatable transform objects through animatable transform parser
//Parse ks in json into animatable transform objects through animatable transform parser public class AnimatableTransformParser { private static final JsonReader.Options NAMES = JsonReader.Options.of( "a", // 1 "p", // 2 "s", // 3 "rz", // 4 "r", // 5 "o", // 6 "so", // 7 "eo", // 8 "sk", // 9 "sa" // 10 ); private static final JsonReader.Options ANIMATABLE_NAMES = JsonReader.Options.of("k"); 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() == JsonReader.Token.BEGIN_OBJECT; if (isObject) { reader.beginObject(); } while (reader.hasNext()) { switch (reader.selectName(NAMES)) { case 0: // a reader.beginObject(); while (reader.hasNext()) { switch (reader.selectName(ANIMATABLE_NAMES)) { case 0: anchorPoint = AnimatablePathValueParser.parse(reader, composition); break; default: reader.skipName(); reader.skipValue(); } } reader.endObject(); break; case 1: // p position = AnimatablePathValueParser.parseSplitPath(reader, composition); break; case 2: // s scale = AnimatableValueParser.parseScale(reader, composition); break; case 3: // rz composition.addWarning("Lottie doesn't support 3D layers."); case 4: // r 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 5: // o opacity = AnimatableValueParser.parseInteger(reader, composition); break; case 6: // so startOpacity = AnimatableValueParser.parseFloat(reader, composition, false); break; case 7: // eo endOpacity = AnimatableValueParser.parseFloat(reader, composition, false); break; case 8: // sk skew = AnimatableValueParser.parseFloat(reader, composition, false); break; case 9: // sa skewAngle = AnimatableValueParser.parseFloat(reader, composition, false); break; default: reader.skipName(); 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; } //Generates an AnimatableTransform object based on the resolved properties return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity, startOpacity, endOpacity, skew, skewAngle); }
5, Information
- Use of Lottie
- Lottie implementation ideas and source code analysis
- How to understand json parameters
- Lottie - json file parsing
- What else can Lottie do?
6, Harvest
Through the study of this article
- Understand the flow of Lottie animation
- Understand the meaning of each field in lottie json
- Analyze lottie json parsing process
Thank you for reading In the next chapter, we will analyze and learn the implementation of Layer rendering and animation