1. 创建LottieAnimationView
2. 创建LottieDrawable
3. 解析json文件为LottieComposition对象
4. lottieDrawable.setComposition(lottieComposition)
lottieAnimationView.setImageDrawable(lottieDrawable)
5. 播放动画playAnimation()
在onValueChanged时,各个创建好的Drawable会根据需求进行重绘,达到动画的效果。
对于开发者使用非常简单, 可以参考lottie源码中sample的使用。
三、Json字段介绍
我们以Lottie-android提供的demo中的AndroidWave.json和anima.json 为例来学习
object
下面我们看下assets中的字段
object->assets:资源
可以看到object中有layers ,assets中也可能有layers,那边layers这个图层字段包含了什么信息呐。
object->layers:图层
object->layers->ty:图层类型
object->layers->ks:图层变换
“o 、r、 p、 a、 s”中字段相同,都是a、k、x、ix
下面对a、k做说明。x、ix也不知道什么用处。
object->layers->ks-> o 、r、 p、 a、 s
Lottie json的字段基本解释完,下面我们结合Lottie-android的源码看下解析为LottieComposition的过程
四、 解析为LottieComposition
json的解析器,解析为LottieComposition对象
4.1 LottieComposition解析的过程
LottieComposition对象
json的解析器,解析为LottieComposition对象
//lottie json的解析器,解析为LottieComposition对象
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://重点看下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会根据像素密度自动适配
int scaledWidth = (int) (width * scale)
int scaledHeight = (int) (height * scale)
Rect bounds = new Rect(0, 0, scaledWidth, scaledHeight)
//json内容解析完之后,调用composition的init进行LottieComposition字段的赋值,完成json对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()
//遍历解析每个layer图层,
while (reader.hasNext()) {
//图层解析
Layer layer = LayerParser.parse(reader, composition)
if (layer.getLayerType() == Layer.LayerType.IMAGE) {
imageCount++
//加入到layers和map提高效率
layers.add(layer)
layerMap.put(layer.getId(), layer)
reader.endArray()
4.2 layers图层的解析过程
被解析为Layer对象
通过LayerParser把json中layers解析为Layer对象
//通过LayerParser把json中layers解析为Layer对象
public class LayerParser {
//对应 json中layers的字段
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(
private static final JsonReader.Options EFFECTS_NAMES = JsonReader.Options.of(
"ty",
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()
//看下Layer.LayerType的值 对应json中ty
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:
//图层变换,动画的 这个重点看下
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:
//蒙层解析
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:
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.")
//根据上面解析的值,生成对应的Layer对象。这个对象是进行绘制
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 ks图层变换的解析过程
被解析为对象AnimatableTransform
通过AnimatableTransformParser把json中ks解析为AnimatableTransform对象
//通过AnimatableTransformParser把json中ks解析为AnimatableTransform对象
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
//根据解析的属性生成AnimatableTransform对象
return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity, startOpacity, endOpacity, skew, skewAngle)
Lottie 的使用
Lottie实现思路和源码分析
如何看懂json参数
Lottie—json文件解析
Lottie 还可以做这些?
通过本篇的学习
了解Lottie动画的流程
了解lottie json的中每个字段的含义
分析lottie json解析过程
感谢你的阅读
下一篇我们分析学习Layer渲染和动画部分的实现,欢迎关注公众号“音视频开发之旅”,一起学习成长。
欢迎交流
- 2812
-
Android Jetpack
Kotlin
- 1.1w
-
半岛铁盒里的猫
Android
OpenGL
音视频开发
- 672
-
纯爱掌门人
Visual Studio Code
音视频开发
- 1702
-
MoonWebTeam
音视频开发
掘金·金石计划
- 562
-
音视频开发之旅
音视频开发
Android
- 2705
-
音视频开发之旅
FFmpeg
Android
- 2447
-
音视频开发之旅
音视频开发
Android
- 2009
-
音视频开发之旅
音视频开发
Android