blob: 16e0eed62550c375fc7b2638fa219a7f0758726f [file] [log] [blame]
package com.airbnb.lottie;
import android.graphics.PointF;
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.List;
class AnimatablePathValue implements IAnimatablePathValue {
static IAnimatablePathValue createAnimatablePathOrSplitDimensionPath(
JSONObject pointValues, LottieComposition composition) {
if (pointValues.has("k")) {
return new AnimatablePathValue(pointValues, composition);
} else {
return new AnimatableSplitDimensionPathValue(pointValues, composition);
}
}
private final List<Float> keyTimes = new ArrayList<>();
private final List<Interpolator> interpolators = new ArrayList<>();
private final LottieComposition composition;
private PointF initialPoint;
private final SegmentedPath animationPath = new SegmentedPath();
private long delay;
private long duration;
private long startFrame;
private long durationFrames;
/**
* Create a default static animatable path.
*/
AnimatablePathValue(LottieComposition composition) {
this.composition = composition;
this.initialPoint = new PointF(0, 0);
}
AnimatablePathValue(JSONObject pointValues, LottieComposition composition) {
this(pointValues, composition, "k");
}
AnimatablePathValue(JSONObject pointValues, LottieComposition composition,
String jsonKey) {
this.composition = composition;
Object value;
try {
value = pointValues.get(jsonKey);
} catch (JSONException e) {
throw new IllegalArgumentException("Point values have no keyframes.");
}
setupAnimationForValue(value);
}
private void setupAnimationForValue(Object value) {
if (value instanceof JSONArray) {
Object firstObject;
try {
firstObject = ((JSONArray) value).get(0);
} catch (JSONException e) {
throw new IllegalArgumentException("Unable to parse value.");
}
if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("t")) {
// Keyframes
buildAnimationForKeyframes((JSONArray) value);
} else {
// Single Value, no animation
initialPoint = JsonUtils.pointFromJsonArray((JSONArray) value, composition.getScale());
}
}
}
@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) composition.getFrameRate() * 1000);
delay = (long) (startFrame / (float) composition.getFrameRate() * 1000);
break;
}
}
boolean addStartValue = true;
boolean addTimePadding = false;
PointF outPoint = 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 (outPoint != null) {
PointF vertex = outPoint;
animationPath.lineTo(vertex.x, vertex.y);
interpolators.add(new LinearInterpolator());
outPoint = null;
}
float scale = composition.getScale();
PointF startPoint =
keyframe.has("s") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("s"), scale) :
new PointF();
if (addStartValue) {
if (i == 0) {
animationPath.moveTo(startPoint.x, startPoint.y);
initialPoint = startPoint;
} else {
animationPath.lineTo(startPoint.x, startPoint.y);
interpolators.add(new LinearInterpolator());
}
addStartValue = false;
}
if (addTimePadding) {
float holdPercentage = timePercentage - 0.00001f;
keyTimes.add(holdPercentage);
addTimePadding = false;
}
PointF cp1;
PointF cp2;
if (keyframe.has("e")) {
cp1 = keyframe.has("to") ?
JsonUtils.pointFromJsonArray(keyframe.getJSONArray("to"), scale) : null;
cp2 = keyframe.has("ti") ?
JsonUtils.pointFromJsonArray(keyframe.getJSONArray("ti"), scale) : null;
PointF vertex = JsonUtils.pointFromJsonArray(keyframe.getJSONArray("e"), scale);
if (cp1 != null && cp2 != null && cp1.length() != 0 && cp2.length() != 0) {
animationPath.cubicTo(
startPoint.x + cp1.x, startPoint.y + cp1.y,
vertex.x + cp2.x, vertex.y + cp2.y,
vertex.x, vertex.y);
} else {
animationPath.lineTo(vertex.x, vertex.y);
}
Interpolator interpolator;
if (keyframe.has("o") && keyframe.has("i")) {
cp1 = JsonUtils.pointValueFromJsonObject(keyframe.getJSONObject("o"), scale);
cp2 = JsonUtils.pointValueFromJsonObject(keyframe.getJSONObject("i"), scale);
interpolator = PathInterpolatorCompat
.create(cp1.x / scale, cp1.y / scale, cp2.x / scale, cp2.y / scale);
} else {
interpolator = new LinearInterpolator();
}
interpolators.add(interpolator);
}
keyTimes.add(timePercentage);
if (keyframe.has("h") && keyframe.getInt("h") == 1) {
outPoint = startPoint;
addStartValue = true;
addTimePadding = true;
}
}
} catch (JSONException e) {
throw new IllegalArgumentException("Unable to parse keyframes " + keyframes, e);
}
}
@Override
public KeyframeAnimation<PointF> createAnimation() {
if (!hasAnimation()) {
return new StaticKeyframeAnimation<>(initialPoint);
}
KeyframeAnimation<PointF> animation =
new PathKeyframeAnimation(duration, composition, keyTimes, animationPath, interpolators);
animation.setStartDelay(delay);
return animation;
}
@Override
public boolean hasAnimation() {
return animationPath.hasSegments();
}
@Override
public PointF getInitialPoint() {
return initialPoint;
}
@Override
public String toString() {
return "initialPoint=" + initialPoint;
}
}