| package com.airbnb.lottie.parser; |
| |
| import com.airbnb.lottie.LottieComposition; |
| import com.airbnb.lottie.animation.keyframe.PathKeyframe; |
| import com.airbnb.lottie.parser.moshi.JsonReader; |
| import com.airbnb.lottie.value.Keyframe; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| class KeyframesParser { |
| |
| static JsonReader.Options NAMES = JsonReader.Options.of("k"); |
| |
| private KeyframesParser() { |
| } |
| |
| static <T> List<Keyframe<T>> parse(JsonReader reader, |
| LottieComposition composition, float scale, ValueParser<T> valueParser) |
| throws IOException { |
| List<Keyframe<T>> keyframes = new ArrayList<>(); |
| |
| if (reader.peek() == JsonReader.Token.STRING) { |
| composition.addWarning("Lottie doesn't support expressions."); |
| return keyframes; |
| } |
| |
| reader.beginObject(); |
| while (reader.hasNext()) { |
| switch (reader.selectName(NAMES)) { |
| case 0: |
| if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { |
| reader.beginArray(); |
| |
| if (reader.peek() == JsonReader.Token.NUMBER) { |
| // For properties in which the static value is an array of numbers. |
| keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, false)); |
| } else { |
| while (reader.hasNext()) { |
| keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, true)); |
| } |
| } |
| reader.endArray(); |
| } else { |
| keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, false)); |
| } |
| break; |
| default: |
| reader.skipValue(); |
| } |
| } |
| reader.endObject(); |
| |
| setEndFrames(keyframes); |
| return keyframes; |
| } |
| |
| /** |
| * The json doesn't include end frames. The data can be taken from the start frame of the next |
| * keyframe though. |
| */ |
| public static <T> void setEndFrames(List<? extends Keyframe<T>> keyframes) { |
| int size = keyframes.size(); |
| for (int i = 0; i < size - 1; i++) { |
| // In the json, the keyframes only contain their starting frame. |
| Keyframe<T> keyframe = keyframes.get(i); |
| Keyframe<T> nextKeyframe = keyframes.get(i + 1); |
| keyframe.endFrame = nextKeyframe.startFrame; |
| if (keyframe.endValue == null && nextKeyframe.startValue != null) { |
| keyframe.endValue = nextKeyframe.startValue; |
| if (keyframe instanceof PathKeyframe) { |
| ((PathKeyframe) keyframe).createPath(); |
| } |
| } |
| } |
| Keyframe<?> lastKeyframe = keyframes.get(size - 1); |
| if ((lastKeyframe.startValue == null || lastKeyframe.endValue == null) && keyframes.size() > 1) { |
| // The only purpose the last keyframe has is to provide the end frame of the previous |
| // keyframe. |
| //noinspection SuspiciousMethodCalls |
| keyframes.remove(lastKeyframe); |
| } |
| } |
| } |