blob: 6fc9890802b5ecc9fa3a0a81d908b09e7057f609 [file] [log] [blame]
package com.airbnb.lottie;
import android.graphics.PointF;
import android.support.annotation.Nullable;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
abstract class BaseAnimatableValue<V, O> implements AnimatableValue<O> {
final List<V> keyValues = new ArrayList<>();
final List<Float> keyTimes = new ArrayList<>();
final List<Interpolator> interpolators = new ArrayList<>();
long delay;
long duration;
final LottieComposition composition;
private final boolean isDp;
private long startFrame;
private long durationFrames;
private final int frameRate;
V initialValue;
/** Create a default static animatable path. */
BaseAnimatableValue(LottieComposition composition) {
this.composition = composition;
isDp = false;
frameRate = 0;
}
BaseAnimatableValue(@Nullable JSONObject json, int frameRate, LottieComposition composition,
boolean isDp) {
this.frameRate = frameRate;
this.composition = composition;
this.isDp = isDp;
if (json != null) {
init(json);
}
}
void init(JSONObject json) {
try {
Object value = json.get("k");
if (value instanceof JSONArray &&
((JSONArray) value).get(0) instanceof JSONObject &&
((JSONArray) value).getJSONObject(0).has("t")) {
buildAnimationForKeyframes((JSONArray) value);
} else {
initialValue = valueFromObject(value, getScale());
}
} catch (JSONException e) {
throw new IllegalStateException("Unable to parse json " + json, e);
}
}
@SuppressWarnings("Duplicates")
private void buildAnimationForKeyframes(JSONArray keyframes) {
try {
for (int i = 0; i < keyframes.length(); i++) {
JSONObject kf = keyframes.getJSONObject(i);
if (kf.has("t")) {
startFrame = kf.getLong("t");
break;
}
}
for (int i = keyframes.length() - 1; i >= 0; i--) {
JSONObject keyframe = keyframes.getJSONObject(i);
if (keyframe.has("t")) {
long endFrame = keyframe.getLong("t");
if (endFrame <= startFrame) {
throw new IllegalStateException(
"Invalid frame compDuration " + startFrame + "->" + endFrame);
}
durationFrames = endFrame - startFrame;
duration = (long) (durationFrames / (float) frameRate * 1000);
delay = (long) (startFrame / (float) frameRate * 1000);
break;
}
}
boolean addStartValue = true;
boolean addTimePadding = false;
V outValue = null;
for (int i = 0; i < keyframes.length(); i++) {
JSONObject keyframe = keyframes.getJSONObject(i);
long frame = keyframe.getLong("t");
float timePercentage = (float) (frame - startFrame) / (float) durationFrames;
if (outValue != null) {
keyValues.add(outValue);
interpolators.add(new LinearInterpolator());
outValue = null;
}
V startValue =
keyframe.has("s") ? valueFromObject(keyframe.getJSONArray("s"), getScale()) : null;
if (addStartValue) {
if (startValue != null) {
if (i == 0) {
//noinspection ResourceAsColor
initialValue = startValue;
}
keyValues.add(startValue);
if (!interpolators.isEmpty()) {
interpolators.add(new LinearInterpolator());
}
}
addStartValue = false;
}
if (addTimePadding) {
float holdPercentage = timePercentage - 0.00001f;
keyTimes.add(holdPercentage);
addTimePadding = false;
}
if (keyframe.has("e")) {
V endValue = valueFromObject(keyframe.getJSONArray("e"), getScale());
keyValues.add(endValue);
/*
Timing function for time interpolation between keyframes.
Should be n - 1 where n is the number of keyframes.
*/
Interpolator timingFunction;
if (keyframe.has("o") && keyframe.has("i")) {
JSONObject timingControlPoint1 = keyframe.getJSONObject("o");
JSONObject timingControlPoint2 = keyframe.getJSONObject("i");
PointF cp1 = JsonUtils.pointValueFromJsonObject(timingControlPoint1, 1);
PointF cp2 = JsonUtils.pointValueFromJsonObject(timingControlPoint2, 1);
timingFunction = PathInterpolatorCompat.create(cp1.x, cp1.y, cp2.x, cp2.y);
} else {
timingFunction = new LinearInterpolator();
}
interpolators.add(timingFunction);
}
keyTimes.add(timePercentage);
if (keyframe.has("h") && keyframe.getInt("h") == 1) {
outValue = startValue;
addStartValue = true;
addTimePadding = true;
}
}
} catch (JSONException e) {
throw new IllegalArgumentException("Unable to parse values.", e);
}
}
private float getScale() {
return isDp ? composition.getScale() : 1f;
}
O convertType(V value) {
//noinspection unchecked
return (O) value;
}
public boolean hasAnimation() {
return !keyValues.isEmpty();
}
public O getInitialValue() {
return convertType(initialValue);
}
protected abstract V valueFromObject(Object object, float scale) throws JSONException;
public abstract KeyframeAnimation<O> createAnimation();
@Override public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("initialValue=").append(initialValue);
if (!keyValues.isEmpty()) {
sb.append(", values=").append(Arrays.toString(keyTimes.toArray()));
}
return sb.toString();
}
}