From 7d80f43caeb1822a1a34e361b2708a8ba01bb745 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Tue, 17 Aug 2021 13:37:03 -0700 Subject: [PATCH] Added support for drop shadows (#1860) --- .../com/airbnb/lottie/LottieProperty.java | 33 ++++++ .../animation/content/BaseStrokeContent.java | 23 +++- .../lottie/animation/content/FillContent.java | 26 ++++- .../content/GradientFillContent.java | 22 +++- .../keyframe/DropShadowKeyframeAnimation.java | 100 ++++++++++++++++++ .../airbnb/lottie/model/layer/BaseLayer.java | 6 ++ .../lottie/model/layer/CompositionLayer.java | 2 +- .../com/airbnb/lottie/model/layer/Layer.java | 10 +- .../airbnb/lottie/model/layer/ShapeLayer.java | 9 ++ .../lottie/parser/DropShadowEffect.java | 41 +++++++ .../lottie/parser/DropShadowEffectParser.java | 87 +++++++++++++++ .../com/airbnb/lottie/parser/LayerParser.java | 7 +- .../com/airbnb/lottie/samples/LottieTest.kt | 35 ++++++ .../src/main/assets/Tests/AnimatedShadow.json | 1 + .../src/main/assets/Tests/StaticShadow.json | 1 + 15 files changed, 388 insertions(+), 15 deletions(-) create mode 100644 lottie/src/main/java/com/airbnb/lottie/animation/keyframe/DropShadowKeyframeAnimation.java create mode 100644 lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffect.java create mode 100644 lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffectParser.java create mode 100644 sample/src/main/assets/Tests/AnimatedShadow.json create mode 100644 sample/src/main/assets/Tests/StaticShadow.json diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java index 4932deda70..09d96ae9b6 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java @@ -74,6 +74,7 @@ public interface LottieProperty { * [0,100] */ Integer OPACITY = 4; + Integer DROP_SHADOW_COLOR = 5; /** * In Px */ @@ -167,6 +168,38 @@ public interface LottieProperty { * In Dp */ Float TEXT_SIZE = 14f; + /** + * [0,100] + * Lottie Android resolved drop shadows on drawing content such as fills and strokes. + * If a drop shadow is applied to a layer, the dynamic properties must be set on all + * of its child elements that draw. The easiest way to do this is to append "**" to your + * Keypath after the layer name. + */ + Float DROP_SHADOW_OPACITY = 15f; + /** + * Degrees from 12 o'clock. + * Lottie Android resolved drop shadows on drawing content such as fills and strokes. + * If a drop shadow is applied to a layer, the dynamic properties must be set on all + * of its child elements that draw. The easiest way to do this is to append "**" to your + * Keypath after the layer name. + */ + Float DROP_SHADOW_DIRECTION = 16f; + /** + * In Px + * Lottie Android resolved drop shadows on drawing content such as fills and strokes. + * If a drop shadow is applied to a layer, the dynamic properties must be set on all + * of its child elements that draw. The easiest way to do this is to append "**" to your + * Keypath after the layer name. + */ + Float DROP_SHADOW_DISTANCE = 17f; + /** + * In Px + * Lottie Android resolved drop shadows on drawing content such as fills and strokes. + * If a drop shadow is applied to a layer, the dynamic properties must be set on all + * of its child elements that draw. The easiest way to do this is to append "**" to your + * Keypath after the layer name. + */ + Float DROP_SHADOW_RADIUS = 18f; ColorFilter COLOR_FILTER = new ColorFilter(); /** diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java index c2572382cf..643d9ce8cb 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java @@ -20,6 +20,7 @@ import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.animation.LPaint; import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation; +import com.airbnb.lottie.animation.keyframe.DropShadowKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.IntegerKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation; @@ -57,6 +58,8 @@ public abstract class BaseStrokeContent @Nullable private BaseKeyframeAnimation blurAnimation; float blurMaskFilterRadius = 0f; + @Nullable private DropShadowKeyframeAnimation dropShadowAnimation; + BaseStrokeContent(final LottieDrawable lottieDrawable, BaseLayer layer, Paint.Cap cap, Paint.Join join, float miterLimit, AnimatableIntegerValue opacity, AnimatableFloatValue width, List dashPattern, AnimatableFloatValue offset) { @@ -102,13 +105,14 @@ public abstract class BaseStrokeContent dashPatternOffsetAnimation.addUpdateListener(this); } - if (layer.getBlurEffect() == null) { - blurAnimation = null; - } else { + if (layer.getBlurEffect() != null) { blurAnimation = layer.getBlurEffect().getBlurriness().createAnimation(); blurAnimation.addUpdateListener(this); layer.addAnimation(blurAnimation); } + if (layer.getDropShadowEffect() != null) { + dropShadowAnimation = new DropShadowKeyframeAnimation(this, layer, layer.getDropShadowEffect()); + } } @Override public void onValueChanged() { @@ -180,6 +184,9 @@ public abstract class BaseStrokeContent } blurMaskFilterRadius = blurRadius; } + if (dropShadowAnimation != null) { + dropShadowAnimation.applyTo(paint); + } for (int i = 0; i < pathGroups.size(); i++) { PathGroup pathGroup = pathGroups.get(i); @@ -359,6 +366,16 @@ public void addValueCallback(T property, @Nullable LottieValueCallback ca blurAnimation.addUpdateListener(this); layer.addAnimation(blurAnimation); } + } else if (property == LottieProperty.DROP_SHADOW_COLOR && dropShadowAnimation != null) { + dropShadowAnimation.setColorCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_OPACITY && dropShadowAnimation != null) { + dropShadowAnimation.setOpacityCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_DIRECTION && dropShadowAnimation != null) { + dropShadowAnimation.setDirectionCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_DISTANCE && dropShadowAnimation != null) { + dropShadowAnimation.setDistanceCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_RADIUS && dropShadowAnimation != null) { + dropShadowAnimation.setRadiusCallback((LottieValueCallback) callback); } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java index 0578295f20..35f472faaf 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java @@ -18,7 +18,7 @@ import com.airbnb.lottie.animation.LPaint; import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.ColorKeyframeAnimation; -import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation; +import com.airbnb.lottie.animation.keyframe.DropShadowKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation; import com.airbnb.lottie.model.KeyPath; import com.airbnb.lottie.model.content.ShapeFill; @@ -44,18 +44,21 @@ public class FillContent @Nullable private BaseKeyframeAnimation blurAnimation; float blurMaskFilterRadius; + @Nullable private DropShadowKeyframeAnimation dropShadowAnimation; + public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFill fill) { this.layer = layer; name = fill.getName(); hidden = fill.isHidden(); this.lottieDrawable = lottieDrawable; - if (layer.getBlurEffect() == null) { - blurAnimation = null; - } else { + if (layer.getBlurEffect() != null) { blurAnimation = layer.getBlurEffect().getBlurriness().createAnimation(); blurAnimation.addUpdateListener(this); layer.addAnimation(blurAnimation); } + if (layer.getDropShadowEffect() != null) { + dropShadowAnimation = new DropShadowKeyframeAnimation(this, layer, layer.getDropShadowEffect()); + } if (fill.getColor() == null || fill.getOpacity() == null) { colorAnimation = null; @@ -107,12 +110,15 @@ public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFi float blurRadius = blurAnimation.getValue(); if (blurRadius == 0f) { paint.setMaskFilter(null); - } else if (blurRadius != blurMaskFilterRadius){ + } else if (blurRadius != blurMaskFilterRadius) { BlurMaskFilter blur = layer.getBlurMaskFilter(blurRadius); paint.setMaskFilter(blur); } blurMaskFilterRadius = blurRadius; } + if (dropShadowAnimation != null) { + dropShadowAnimation.applyTo(paint); + } path.reset(); for (int i = 0; i < paths.size(); i++) { @@ -173,6 +179,16 @@ public void addValueCallback(T property, @Nullable LottieValueCallback ca blurAnimation.addUpdateListener(this); layer.addAnimation(blurAnimation); } + } else if (property == LottieProperty.DROP_SHADOW_COLOR && dropShadowAnimation != null) { + dropShadowAnimation.setColorCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_OPACITY && dropShadowAnimation != null) { + dropShadowAnimation.setOpacityCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_DIRECTION && dropShadowAnimation != null) { + dropShadowAnimation.setDirectionCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_DISTANCE && dropShadowAnimation != null) { + dropShadowAnimation.setDistanceCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_RADIUS && dropShadowAnimation != null) { + dropShadowAnimation.setRadiusCallback((LottieValueCallback) callback); } } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java index c5a447862c..8d3c5cae33 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java @@ -23,6 +23,7 @@ import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.animation.LPaint; import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation; +import com.airbnb.lottie.animation.keyframe.DropShadowKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation; import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation; import com.airbnb.lottie.model.KeyPath; @@ -62,6 +63,7 @@ public class GradientFillContent private final int cacheSteps; @Nullable private BaseKeyframeAnimation blurAnimation; float blurMaskFilterRadius = 0f; + @Nullable private DropShadowKeyframeAnimation dropShadowAnimation; public GradientFillContent(final LottieDrawable lottieDrawable, BaseLayer layer, GradientFill fill) { this.layer = layer; @@ -88,13 +90,14 @@ public GradientFillContent(final LottieDrawable lottieDrawable, BaseLayer layer, endPointAnimation.addUpdateListener(this); layer.addAnimation(endPointAnimation); - if (layer.getBlurEffect() == null) { - blurAnimation = null; - } else { + if (layer.getBlurEffect() != null) { blurAnimation = layer.getBlurEffect().getBlurriness().createAnimation(); blurAnimation.addUpdateListener(this); layer.addAnimation(blurAnimation); } + if (layer.getDropShadowEffect() != null) { + dropShadowAnimation = new DropShadowKeyframeAnimation(this, layer, layer.getDropShadowEffect()); + } } @Override public void onValueChanged() { @@ -145,6 +148,9 @@ public GradientFillContent(final LottieDrawable lottieDrawable, BaseLayer layer, } blurMaskFilterRadius = blurRadius; } + if (dropShadowAnimation != null) { + dropShadowAnimation.applyTo(paint); + } int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255); paint.setAlpha(clamp(alpha, 0, 255)); @@ -295,6 +301,16 @@ public void addValueCallback(T property, @Nullable LottieValueCallback ca blurAnimation.addUpdateListener(this); layer.addAnimation(blurAnimation); } + } else if (property == LottieProperty.DROP_SHADOW_COLOR && dropShadowAnimation != null) { + dropShadowAnimation.setColorCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_OPACITY && dropShadowAnimation != null) { + dropShadowAnimation.setOpacityCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_DIRECTION && dropShadowAnimation != null) { + dropShadowAnimation.setDirectionCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_DISTANCE && dropShadowAnimation != null) { + dropShadowAnimation.setDistanceCallback((LottieValueCallback) callback); + } else if (property == LottieProperty.DROP_SHADOW_RADIUS && dropShadowAnimation != null) { + dropShadowAnimation.setRadiusCallback((LottieValueCallback) callback); } } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/DropShadowKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/DropShadowKeyframeAnimation.java new file mode 100644 index 0000000000..128913a9a3 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/DropShadowKeyframeAnimation.java @@ -0,0 +1,100 @@ +package com.airbnb.lottie.animation.keyframe; + +import android.graphics.Color; +import android.graphics.Paint; + +import androidx.annotation.Nullable; + +import com.airbnb.lottie.model.layer.BaseLayer; +import com.airbnb.lottie.parser.DropShadowEffect; +import com.airbnb.lottie.value.LottieFrameInfo; +import com.airbnb.lottie.value.LottieValueCallback; + +public class DropShadowKeyframeAnimation implements BaseKeyframeAnimation.AnimationListener { + private static final double DEG_TO_RAD = Math.PI / 180.0; + + private final BaseKeyframeAnimation.AnimationListener listener; + private final BaseKeyframeAnimation color; + private final BaseKeyframeAnimation opacity; + private final BaseKeyframeAnimation direction; + private final BaseKeyframeAnimation distance; + private final BaseKeyframeAnimation radius; + + private boolean isDirty = true; + + public DropShadowKeyframeAnimation(BaseKeyframeAnimation.AnimationListener listener, BaseLayer layer, DropShadowEffect dropShadowEffect) { + this.listener = listener; + color = dropShadowEffect.getColor().createAnimation(); + color.addUpdateListener(this); + layer.addAnimation(color); + opacity = dropShadowEffect.getOpacity().createAnimation(); + opacity.addUpdateListener(this); + layer.addAnimation(opacity); + direction = dropShadowEffect.getDirection().createAnimation(); + direction.addUpdateListener(this); + layer.addAnimation(direction); + distance = dropShadowEffect.getDistance().createAnimation(); + distance.addUpdateListener(this); + layer.addAnimation(distance); + radius = dropShadowEffect.getRadius().createAnimation(); + radius.addUpdateListener(this); + layer.addAnimation(radius); + } + + @Override public void onValueChanged() { + isDirty = true; + listener.onValueChanged(); + } + + public void applyTo(Paint paint) { + if (!isDirty) { + return; + } + isDirty = false; + + double directionRad = ((double) direction.getValue()) * DEG_TO_RAD; + float distance = this.distance.getValue(); + float x = ((float) Math.sin(directionRad)) * distance; + float y = ((float) Math.cos(directionRad + Math.PI)) * distance; + int baseColor = color.getValue(); + int opacity = Math.round(this.opacity.getValue()); + int color = Color.argb(opacity, Color.red(baseColor), Color.green(baseColor), Color.blue(baseColor)); + float radius = this.radius.getValue(); + paint.setShadowLayer(radius, x, y, color); + } + + public void setColorCallback(@Nullable LottieValueCallback callback) { + color.setValueCallback(callback); + } + + public void setOpacityCallback(@Nullable final LottieValueCallback callback) { + if (callback == null) { + opacity.setValueCallback(null); + return; + } + opacity.setValueCallback(new LottieValueCallback() { + @Nullable + @Override + public Float getValue(LottieFrameInfo frameInfo) { + Float value = callback.getValue(frameInfo); + if (value == null) { + return null; + } + // Convert [0,100] to [0,255] because other dynamic properties use [0,100]. + return value * 2.55f; + } + }); + } + + public void setDirectionCallback(@Nullable LottieValueCallback callback) { + direction.setValueCallback(callback); + } + + public void setDistanceCallback(@Nullable LottieValueCallback callback) { + distance.setValueCallback(callback); + } + + public void setRadiusCallback(@Nullable LottieValueCallback callback) { + radius.setValueCallback(callback); + } +} diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java index 437da4b1c9..336ffb2c72 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java @@ -31,6 +31,7 @@ import com.airbnb.lottie.model.content.BlurEffect; import com.airbnb.lottie.model.content.Mask; import com.airbnb.lottie.model.content.ShapeData; +import com.airbnb.lottie.parser.DropShadowEffect; import com.airbnb.lottie.utils.Logger; import com.airbnb.lottie.utils.Utils; import com.airbnb.lottie.value.LottieValueCallback; @@ -606,6 +607,11 @@ public BlurMaskFilter getBlurMaskFilter(float radius) { return blurMaskFilter; } + @Nullable + public DropShadowEffect getDropShadowEffect() { + return layerModel.getDropShadowEffect(); + } + @Override public void setContents(List contentsBefore, List contentsAfter) { // Do nothing diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java index 18d2a95cdf..c20ae753ad 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java @@ -28,7 +28,7 @@ public class CompositionLayer extends BaseLayer { private final List layers = new ArrayList<>(); private final RectF rect = new RectF(); private final RectF newClipRect = new RectF(); - private Paint layerPaint = new Paint(); + private final Paint layerPaint = new Paint(); @Nullable private Boolean hasMatte; @Nullable private Boolean hasMasks; diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java index f7f55312fa..e2fa79d1b8 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java @@ -10,6 +10,7 @@ import com.airbnb.lottie.model.content.BlurEffect; import com.airbnb.lottie.model.content.ContentModel; import com.airbnb.lottie.model.content.Mask; +import com.airbnb.lottie.parser.DropShadowEffect; import com.airbnb.lottie.value.Keyframe; import java.util.List; @@ -59,6 +60,7 @@ public enum MatteType { private final MatteType matteType; private final boolean hidden; @Nullable private final BlurEffect blurEffect; + @Nullable private final DropShadowEffect dropShadowEffect; public Layer(List shapes, LottieComposition composition, String layerName, long layerId, LayerType layerType, long parentId, @Nullable String refId, List masks, @@ -66,7 +68,8 @@ public Layer(List shapes, LottieComposition composition, String la float timeStretch, float startFrame, int preCompWidth, int preCompHeight, @Nullable AnimatableTextFrame text, @Nullable AnimatableTextProperties textProperties, List> inOutKeyframes, MatteType matteType, - @Nullable AnimatableFloatValue timeRemapping, boolean hidden, @Nullable BlurEffect blurEffect) { + @Nullable AnimatableFloatValue timeRemapping, boolean hidden, @Nullable BlurEffect blurEffect, + @Nullable DropShadowEffect dropShadowEffect) { this.shapes = shapes; this.composition = composition; this.layerName = layerName; @@ -90,6 +93,7 @@ public Layer(List shapes, LottieComposition composition, String la this.timeRemapping = timeRemapping; this.hidden = hidden; this.blurEffect = blurEffect; + this.dropShadowEffect = dropShadowEffect; } LottieComposition getComposition() { @@ -188,6 +192,10 @@ public boolean isHidden() { return blurEffect; } + @Nullable public DropShadowEffect getDropShadowEffect() { + return dropShadowEffect; + } + public String toString(String prefix) { StringBuilder sb = new StringBuilder(); sb.append(prefix).append(getName()).append("\n"); diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java index b8d83a786d..4250d8eda2 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java @@ -13,6 +13,7 @@ import com.airbnb.lottie.model.KeyPath; import com.airbnb.lottie.model.content.BlurEffect; import com.airbnb.lottie.model.content.ShapeGroup; +import com.airbnb.lottie.parser.DropShadowEffect; import java.util.Collections; import java.util.List; @@ -48,6 +49,14 @@ public class ShapeLayer extends BaseLayer { return compositionLayer.getBlurEffect(); } + @Nullable @Override public DropShadowEffect getDropShadowEffect() { + DropShadowEffect layerDropShadow = super.getDropShadowEffect(); + if (layerDropShadow != null) { + return layerDropShadow; + } + return compositionLayer.getDropShadowEffect(); + } + @Override protected void resolveChildKeyPath(KeyPath keyPath, int depth, List accumulator, KeyPath currentPartialKeyPath) { diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffect.java b/lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffect.java new file mode 100644 index 0000000000..4d15a07978 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffect.java @@ -0,0 +1,41 @@ +package com.airbnb.lottie.parser; + +import com.airbnb.lottie.model.animatable.AnimatableColorValue; +import com.airbnb.lottie.model.animatable.AnimatableFloatValue; + +public class DropShadowEffect { + private final AnimatableColorValue color; + private final AnimatableFloatValue opacity; + private final AnimatableFloatValue direction; + private final AnimatableFloatValue distance; + private final AnimatableFloatValue radius; + + DropShadowEffect(AnimatableColorValue color, AnimatableFloatValue opacity, AnimatableFloatValue direction, + AnimatableFloatValue distance, AnimatableFloatValue radius) { + this.color = color; + this.opacity = opacity; + this.direction = direction; + this.distance = distance; + this.radius = radius; + } + + public AnimatableColorValue getColor() { + return color; + } + + public AnimatableFloatValue getOpacity() { + return opacity; + } + + public AnimatableFloatValue getDirection() { + return direction; + } + + public AnimatableFloatValue getDistance() { + return distance; + } + + public AnimatableFloatValue getRadius() { + return radius; + } +} diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffectParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffectParser.java new file mode 100644 index 0000000000..e4bb5d81bd --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/parser/DropShadowEffectParser.java @@ -0,0 +1,87 @@ +package com.airbnb.lottie.parser; + +import androidx.annotation.Nullable; + +import com.airbnb.lottie.LottieComposition; +import com.airbnb.lottie.model.animatable.AnimatableColorValue; +import com.airbnb.lottie.model.animatable.AnimatableFloatValue; +import com.airbnb.lottie.parser.moshi.JsonReader; + +import java.io.IOException; + +public class DropShadowEffectParser { + + private static final JsonReader.Options DROP_SHADOW_EFFECT_NAMES = JsonReader.Options.of( + "ef" + ); + private static final JsonReader.Options INNER_EFFECT_NAMES = JsonReader.Options.of( + "nm", + "v" + ); + + private AnimatableColorValue color; + private AnimatableFloatValue opacity; + private AnimatableFloatValue direction; + private AnimatableFloatValue distance; + private AnimatableFloatValue radius; + + @Nullable + DropShadowEffect parse(JsonReader reader, LottieComposition composition) throws IOException { + while (reader.hasNext()) { + switch (reader.selectName(DROP_SHADOW_EFFECT_NAMES)) { + case 0: + reader.beginArray(); + while (reader.hasNext()) { + maybeParseInnerEffect(reader, composition); + } + reader.endArray(); + break; + default: + reader.skipName(); + reader.skipValue(); + } + } + if (color != null && opacity != null && direction != null && distance != null && radius != null) { + return new DropShadowEffect(color, opacity, direction, distance, radius); + } + return null; + } + + private void maybeParseInnerEffect(JsonReader reader, LottieComposition composition) throws IOException { + String currentEffectName = ""; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.selectName(INNER_EFFECT_NAMES)) { + case 0: + currentEffectName = reader.nextString(); + break; + case 1: + switch (currentEffectName) { + case "Shadow Color": + color = AnimatableValueParser.parseColor(reader, composition); + break; + case "Opacity": + opacity = AnimatableValueParser.parseFloat(reader, composition, false); + break; + case "Direction": + direction = AnimatableValueParser.parseFloat(reader, composition, false); + break; + case "Distance": + distance = AnimatableValueParser.parseFloat(reader, composition); + break; + case "Softness": + radius = AnimatableValueParser.parseFloat(reader, composition); + break; + default: + reader.skipValue(); + break; + } + break; + default: + reader.skipName(); + reader.skipValue(); + } + } + reader.endObject(); + } +} diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java index 5bd7544c24..6881a0265e 100644 --- a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java +++ b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java @@ -59,7 +59,7 @@ public static Layer parse(LottieComposition composition) { Layer.LayerType.PRE_COMP, -1, null, Collections.emptyList(), new AnimatableTransform(), 0, 0, 0, 0, 0, bounds.width(), bounds.height(), null, null, Collections.>emptyList(), - Layer.MatteType.NONE, null, false, null); + Layer.MatteType.NONE, null, false, null, null); } private static final JsonReader.Options TEXT_NAMES = JsonReader.Options.of( @@ -92,6 +92,7 @@ public static Layer parse(JsonReader reader, LottieComposition composition) thro String cl = null; boolean hidden = false; BlurEffect blurEffect = null; + DropShadowEffect dropShadowEffect = null; Layer.MatteType matteType = Layer.MatteType.NONE; AnimatableTransform transform = null; @@ -207,6 +208,8 @@ public static Layer parse(JsonReader reader, LottieComposition composition) thro int type = reader.nextInt(); if (type == 29) { blurEffect = BlurEffectParser.parse(reader, composition); + } else if (type == 25) { + dropShadowEffect = new DropShadowEffectParser().parse(reader, composition); } break; case 1: @@ -284,6 +287,6 @@ public static Layer parse(JsonReader reader, LottieComposition composition) thro return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId, masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startFrame, preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType, - timeRemapping, hidden, blurEffect); + timeRemapping, hidden, blurEffect, dropShadowEffect); } } diff --git a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt index 9d1b1b34fc..af2c9fc02c 100644 --- a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt +++ b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt @@ -714,6 +714,41 @@ class LottieTest { LottieInterpolatedIntegerValue(10, 100), 1f) + testDynamicProperty( + "Drop Shadow Color", + KeyPath("Shape Layer 1", "**"), + LottieProperty.DROP_SHADOW_COLOR, + LottieValueCallback(Color.RED), + assetName = "Tests/AnimatedShadow.json") + + testDynamicProperty( + "Drop Shadow Distance", + KeyPath("Shape Layer 1", "**"), + LottieProperty.DROP_SHADOW_DISTANCE, + LottieValueCallback(30f), + assetName = "Tests/AnimatedShadow.json") + + testDynamicProperty( + "Drop Shadow Direction", + KeyPath("Shape Layer 1", "**"), + LottieProperty.DROP_SHADOW_DIRECTION, + LottieValueCallback(30f), + assetName = "Tests/AnimatedShadow.json") + + testDynamicProperty( + "Drop Shadow Radius", + KeyPath("Shape Layer 1", "**"), + LottieProperty.DROP_SHADOW_RADIUS, + LottieValueCallback(40f), + assetName = "Tests/AnimatedShadow.json") + + testDynamicProperty( + "Drop Shadow Opacity", + KeyPath("Shape Layer 1", "**"), + LottieProperty.DROP_SHADOW_OPACITY, + LottieValueCallback(0.2f), + assetName = "Tests/AnimatedShadow.json") + withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Fill") { drawable -> val value = object : LottieValueCallback>() { override fun getValue(frameInfo: LottieFrameInfo>?): Array { diff --git a/sample/src/main/assets/Tests/AnimatedShadow.json b/sample/src/main/assets/Tests/AnimatedShadow.json new file mode 100644 index 0000000000..ec86bb81dd --- /dev/null +++ b/sample/src/main/assets/Tests/AnimatedShadow.json @@ -0,0 +1 @@ +{"v":"5.7.7","fr":29.9700012207031,"ip":0,"op":181.000007372281,"w":375,"h":375,"nm":"Square","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow2","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0,0,0,1]},{"t":151.000006150356,"s":[0,0.371857702732,1,1]}],"ix":1}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[127.5]},{"t":151.000006150356,"s":[127.5]}],"ix":2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":151.000006150356,"s":[360]}],"ix":3}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[10]},{"t":151.000006150356,"s":[15]}],"ix":4}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[20]},{"t":151.000006150356,"s":[50]}],"ix":5}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[217.641,217.641],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4.68,-1.68],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":184.000007494474,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/sample/src/main/assets/Tests/StaticShadow.json b/sample/src/main/assets/Tests/StaticShadow.json new file mode 100644 index 0000000000..e842acdf53 --- /dev/null +++ b/sample/src/main/assets/Tests/StaticShadow.json @@ -0,0 +1 @@ +{"v":"5.7.7","fr":29.9700012207031,"ip":0,"op":181.000007372281,"w":375,"h":375,"nm":"Square","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow2","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,1,0.609851837158,1],"ix":1}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":127.5,"ix":2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":10,"ix":4}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":20,"ix":5}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[217.641,217.641],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4.68,-1.68],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":184.000007494474,"st":0,"bm":0}],"markers":[]} \ No newline at end of file