| 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(); |
| testFrameBoundary2(); |
| 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 testFrameBoundary2() { |
| LottieAnimationView animationView = new LottieAnimationView(context); |
| LottieComposition composition = |
| LottieComposition.Factory.fromFileSync(context, "Tests/RGB.json"); |
| animationView.setComposition(composition); |
| ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( |
| ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
| |
| animationView.setFrame(0); |
| recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 0 Red", params); |
| animationView.setFrame(1); |
| recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 1 Green", params); |
| animationView.setFrame(2); |
| recordSnapshot(animationView, 1080, "android", "Frame Boundary", "Frame 2 Blue", 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()); |
| } |
| } |