blob: d110ee083fdf08daa90c896fb161e8eeaf8b0e64 [file] [log] [blame]
package com.airbnb.lottie;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PointF;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.airbnb.happo.SnapshotProvider;
import com.airbnb.lottie.model.KeyPath;
import com.airbnb.lottie.value.LottieInterpolatedIntegerValue;
import com.airbnb.lottie.value.LottieRelativeFloatValueCallback;
import com.airbnb.lottie.value.LottieRelativePointValueCallback;
import com.airbnb.lottie.value.LottieValueCallback;
import com.airbnb.lottie.value.ScaleXY;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class LottieSnapshotProvider extends SnapshotProvider {
private static final float[] PROGRESS = {0f, 0.25f, 0.5f, 0.75f, 1f};
private static final int CORES = 1; //Runtime.getRuntime().availableProcessors();
private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(CORES, CORES, 15, TimeUnit.MINUTES, queue);
private final Context context;
// Bitmap to return from an ImageAssetDelegate to make testing animations with images easier.
private final Bitmap dummyBitmap;
private int remainingTasks = 0;
LottieSnapshotProvider(Context context) {
this.context = context;
dummyBitmap = BitmapFactory.decodeResource(context.getResources(), com.airbnb.lottie.samples.R
.drawable.airbnb);
}
@Override
public void beginSnapshotting() {
try {
snapshotAssets(context.getAssets().list(""));
String[] tests = context.getAssets().list("Tests");
for (int i = 0; i < tests.length; i++) {
tests[i] = "Tests/" + tests[i];
}
snapshotAssets(tests);
String[] lottiefiles = context.getAssets().list("lottiefiles");
for (int i = 0; i < lottiefiles.length; i++) {
lottiefiles[i] = "lottiefiles/" + lottiefiles[i];
}
snapshotAssets(lottiefiles);
} catch (IOException e) {
onError(e);
}
testFrameBoundary();
testScaleTypes();
testDynamicProperties();
}
private void snapshotAssets(String[] animations) {
File dir = new File(Environment.getExternalStorageDirectory() + "/Snapshots");
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
for (File file : dir.listFiles()) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
for (final String animation : animations) {
if (!animation.contains(".json")) {
continue;
}
remainingTasks += 1;
executor.execute(new Runnable() {
@Override
public void run() {
runAnimation(animation);
decrementAndCompleteIfDone();
}
});
}
}
private void runAnimation(final String name) {
LottieComposition composition = LottieComposition.Factory.fromFileSync(context, name);
if (composition.getBounds().width() > 4 * Resources.getSystem().getDisplayMetrics().widthPixels ||
composition.getBounds().height() > 4 * Resources.getSystem().getDisplayMetrics().heightPixels) {
Log.d("Happo", "" + name + " is too large. Skipping (" + composition.getBounds().width() +
"x" + composition.getBounds().height() + ")");
return;
}
drawComposition(composition, name);
}
private void drawComposition(LottieComposition composition, String name) {
LottieAnimationView view = new LottieAnimationView(context);
view.setImageAssetDelegate(new ImageAssetDelegate() {
@Override public Bitmap fetchBitmap(LottieImageAsset asset) {
return dummyBitmap;
}
});
view.setComposition(composition);
for (float progress : PROGRESS) {
view.setProgress(progress);
recordSnapshot(view, 1080, "android", name, Integer.toString((int) (progress * 100)));
}
}
@Override
public void stopSnapshotting() {
queue.clear();
}
private void decrementAndCompleteIfDone() {
remainingTasks--;
Log.d("Happo", "There are " + remainingTasks + " remaining tasks.");
if (remainingTasks < 0) {
throw new IllegalStateException("Remaining tasks cannot be negative.");
}
if (remainingTasks == 0) {
onComplete();
}
}
private void testScaleTypes() {
LottieComposition composition = LottieComposition.Factory.fromFileSync(
context, "LottieLogo1.json");
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
testScaleTypeView(context, composition, "Wrap Content", params, null, null);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(300));
testScaleTypeView(context, composition, "300x300 @4x", params, null, 4f);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(300));
testScaleTypeView(context, composition, "300x300 centerCrop", params,
ImageView.ScaleType.CENTER_CROP, null);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(300));
testScaleTypeView(context, composition, "300x300 centerInside", params,
ImageView.ScaleType.CENTER_INSIDE, null);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(300));
testScaleTypeView(context, composition, "300x300 centerInside @2x", params,
ImageView.ScaleType.CENTER_INSIDE, 2f);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(300));
testScaleTypeView(context, composition, "300x300 centerCrop @2x", params,
ImageView.ScaleType.CENTER_CROP, 2f);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(300));
testScaleTypeView(context, composition, "300x300 @2x", params,
null, 2f);
params = new FrameLayout.LayoutParams(dpToPx(600), dpToPx(300));
testScaleTypeView(context, composition, "600x300 centerInside", params,
ImageView.ScaleType.CENTER_INSIDE, null);
params = new FrameLayout.LayoutParams(dpToPx(300), dpToPx(600));
testScaleTypeView(context, composition, "300x600 centerInside", params,
ImageView.ScaleType.CENTER_INSIDE, null);
params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
testScaleTypeView(context, composition, "Match Parent", params, null, null);
}
private void testScaleTypeView(Context context, LottieComposition composition,
String name, FrameLayout.LayoutParams params, @Nullable ImageView.ScaleType scaleType,
@Nullable Float scale) {
FrameLayout container = new FrameLayout(context);
LottieAnimationView animationView = new LottieAnimationView(context);
animationView.setComposition(composition);
animationView.setProgress(1f);
if (scaleType != null) {
animationView.setScaleType(scaleType);
}
if (scale != null) {
animationView.setScale(scale);
}
container.addView(animationView, params);
recordSnapshot(container, 1080, "android", "Scale Types", name, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
private void testFrameBoundary() {
LottieAnimationView animationView = new LottieAnimationView(context);
LottieComposition composition =
LottieComposition.Factory.fromFileSync(context, "Tests/Frame.json");
animationView.setComposition(composition);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
animationView.setFrame(16);
recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 16 Red", params);
animationView.setFrame(17);
recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 17 Blue", params);
animationView.setFrame(50);
recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 50 Blue", params);
animationView.setFrame(51);
recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 51 Green", params);
}
private void testDynamicProperties() {
testDynamicProperty(
"Fill color (Green)",
new KeyPath("Shape Layer 1", "Rectangle", "Fill 1"),
LottieProperty.COLOR,
new LottieValueCallback<>(Color.GREEN));
testDynamicProperty(
"Fill color (Yellow)",
new KeyPath("Shape Layer 1", "Rectangle", "Fill 1"),
LottieProperty.COLOR,
new LottieValueCallback<>(Color.YELLOW));
testDynamicProperty(
"Fill opacity",
new KeyPath("Shape Layer 1", "Rectangle", "Fill 1"),
LottieProperty.OPACITY,
new LottieValueCallback<>(50));
testDynamicProperty(
"Stroke color",
new KeyPath("Shape Layer 1", "Rectangle", "Stroke 1"),
LottieProperty.STROKE_COLOR,
new LottieValueCallback<>(Color.GREEN));
testDynamicProperty(
"Stroke width",
new KeyPath("Shape Layer 1", "Rectangle", "Stroke 1"),
LottieProperty.STROKE_WIDTH,
new LottieRelativeFloatValueCallback(50f));
testDynamicProperty(
"Stroke opacity",
new KeyPath("Shape Layer 1", "Rectangle", "Stroke 1"),
LottieProperty.OPACITY,
new LottieValueCallback<>(50));
testDynamicProperty(
"Transform anchor point",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_ANCHOR_POINT,
new LottieRelativePointValueCallback(new PointF(20f, 20f)));
testDynamicProperty(
"Transform position",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_POSITION,
new LottieRelativePointValueCallback(new PointF(20f, 20f)));
testDynamicProperty(
"Transform position (relative)",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_POSITION,
new LottieRelativePointValueCallback(new PointF(20f, 20f)));
testDynamicProperty(
"Transform opacity",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_OPACITY,
new LottieValueCallback<>(50));
testDynamicProperty(
"Transform rotation",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_ROTATION,
new LottieValueCallback<>(45f));
testDynamicProperty(
"Transform scale",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_SCALE,
new LottieValueCallback<>(new ScaleXY(0.5f, 0.5f)));
testDynamicProperty(
"Ellipse position",
new KeyPath("Shape Layer 1", "Ellipse", "Ellipse Path 1"),
LottieProperty.POSITION,
new LottieRelativePointValueCallback(new PointF(20f, 20f)));
testDynamicProperty(
"Ellipse size",
new KeyPath("Shape Layer 1", "Ellipse", "Ellipse Path 1"),
LottieProperty.ELLIPSE_SIZE,
new LottieRelativePointValueCallback(new PointF(40f, 60f)));
testDynamicProperty(
"Star points",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POLYSTAR_POINTS,
new LottieValueCallback<>(8f));
testDynamicProperty(
"Star rotation",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POLYSTAR_ROTATION,
new LottieValueCallback<>(10f));
testDynamicProperty(
"Star position",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POSITION,
new LottieRelativePointValueCallback(new PointF(20f, 20f)));
testDynamicProperty(
"Star inner radius",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POLYSTAR_INNER_RADIUS,
new LottieValueCallback<>(10f));
testDynamicProperty(
"Star inner roundedness",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POLYSTAR_INNER_ROUNDEDNESS,
new LottieValueCallback<>(100f));
testDynamicProperty(
"Star outer radius",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POLYSTAR_OUTER_RADIUS,
new LottieValueCallback<>(60f));
testDynamicProperty(
"Star outer roundedness",
new KeyPath("Shape Layer 1", "Star", "Polystar Path 1"),
LottieProperty.POLYSTAR_OUTER_ROUNDEDNESS,
new LottieValueCallback<>(100f));
testDynamicProperty(
"Polygon points",
new KeyPath("Shape Layer 1", "Polygon", "Polystar Path 1"),
LottieProperty.POLYSTAR_POINTS,
new LottieValueCallback<>(8f));
testDynamicProperty(
"Polygon rotation",
new KeyPath("Shape Layer 1", "Polygon", "Polystar Path 1"),
LottieProperty.POLYSTAR_ROTATION,
new LottieValueCallback<>(10f));
testDynamicProperty(
"Polygon position",
new KeyPath("Shape Layer 1", "Polygon", "Polystar Path 1"),
LottieProperty.POSITION,
new LottieRelativePointValueCallback(new PointF(20f, 20f)));
testDynamicProperty(
"Polygon radius",
new KeyPath("Shape Layer 1", "Polygon", "Polystar Path 1"),
LottieProperty.POLYSTAR_OUTER_RADIUS,
new LottieRelativeFloatValueCallback(60f));
testDynamicProperty(
"Polygon roundedness",
new KeyPath("Shape Layer 1", "Polygon", "Polystar Path 1"),
LottieProperty.POLYSTAR_OUTER_ROUNDEDNESS,
new LottieValueCallback<>(100f));
testDynamicProperty(
"Repeater transform position",
new KeyPath("Shape Layer 1", "Repeater Shape", "Repeater 1"),
LottieProperty.TRANSFORM_POSITION,
new LottieRelativePointValueCallback(new PointF(100f, 100f)));
testDynamicProperty(
"Repeater transform start opacity",
new KeyPath("Shape Layer 1", "Repeater Shape", "Repeater 1"),
LottieProperty.TRANSFORM_START_OPACITY,
new LottieValueCallback<>(25f));
testDynamicProperty(
"Repeater transform end opacity",
new KeyPath("Shape Layer 1", "Repeater Shape", "Repeater 1"),
LottieProperty.TRANSFORM_END_OPACITY,
new LottieValueCallback<>(25f));
testDynamicProperty(
"Repeater transform rotation",
new KeyPath("Shape Layer 1", "Repeater Shape", "Repeater 1"),
LottieProperty.TRANSFORM_ROTATION,
new LottieValueCallback<>(45f));
testDynamicProperty(
"Repeater transform scale",
new KeyPath("Shape Layer 1", "Repeater Shape", "Repeater 1"),
LottieProperty.TRANSFORM_SCALE,
new LottieValueCallback<>(new ScaleXY(2f, 2f)));
testDynamicProperty(
"Time remapping",
new KeyPath("Circle 1"),
LottieProperty.TIME_REMAP,
new LottieValueCallback<>(1f));
testDynamicProperty(
"Color Filter",
new KeyPath("**"),
LottieProperty.COLOR_FILTER,
new LottieValueCallback<ColorFilter>(new SimpleColorFilter(Color.GREEN)));
testDynamicProperty(
"Opacity interpolation (0)",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_OPACITY,
new LottieInterpolatedIntegerValue(10, 100),
0f);
testDynamicProperty(
"Opacity interpolation (0.5)",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_OPACITY,
new LottieInterpolatedIntegerValue(10, 100),
0.5f);
testDynamicProperty(
"Opacity interpolation (1)",
new KeyPath("Shape Layer 1", "Rectangle"),
LottieProperty.TRANSFORM_OPACITY,
new LottieInterpolatedIntegerValue(10, 100),
1f);
}
private <T> void testDynamicProperty(
String name, KeyPath keyPath, T property, LottieValueCallback<T> callback) {
testDynamicProperty(name, keyPath, property, callback, 0f);
}
private <T> void testDynamicProperty(
String name, KeyPath keyPath, T property, LottieValueCallback<T> callback, float progress) {
LottieAnimationView animationView = new LottieAnimationView(context);
LottieComposition composition =
LottieComposition.Factory.fromFileSync(context, "Tests/Shapes.json");
animationView.setComposition(composition);
animationView.setProgress(progress);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
animationView.addValueCallback(keyPath, property, callback);
recordSnapshot(animationView, 1080, "android", "Dynamic Properties", name, params);
}
private int dpToPx(int dp) {
Resources resources = context.getResources();
return (int) TypedValue.applyDimension(1, (float) dp, resources.getDisplayMetrics());
}
}