Skip to content

Commit

Permalink
Added the ability to scale animations (airbnb#175)
Browse files Browse the repository at this point in the history
This can be leveraged to:
A) clean up the dp scale code
B) Improve performance for animations that are larger than the view they are being rendered in to
  • Loading branch information
gpeal authored Mar 6, 2017
1 parent e72ba6c commit c925806
Show file tree
Hide file tree
Showing 43 changed files with 117 additions and 73 deletions.
Binary file modified LottieSample/screenshots/9squares-AlBoardman.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/EmptyState.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/HamburgerArrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/LottieLogo1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/LottieLogo2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/MotionCorpse-Jrcanest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/PinJump.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_CheckSwitch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_Fill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_KeyframeTypes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_Laugh4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_LoopPlayOnce.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_Parenting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_Precomps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_ShapeTypes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_SplitDimensions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_Stroke.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_TrackMattes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/Tests_TrimPaths.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/TwitterHeart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/WeAccept.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/same_composition_first_run_PinJump.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified LottieSample/screenshots/test_changing_compositions_PinJump.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class AnimationLinearLayout extends LinearLayout {
private static final float[] DEFAULT_ANIMATED_PROGRESS = {0f, 0.05f, 0.10f, 0.2f, 0.3f, 0.4f,
0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.95f, 1f};
private static final int DESIRED_WIDTH = 500;

public AnimationLinearLayout(Context context) {
super(context);
Expand All @@ -36,7 +37,6 @@ private void addViewsFor(float progress) {

LottieAnimationView animationView = new LottieAnimationView(getContext());
animationView.setProgress(progress);
lp = generateDefaultLayoutParams();
addView(animationView);
}

Expand All @@ -57,6 +57,8 @@ void setComposition(LottieComposition composition) {
continue;
}
((LottieAnimationView) child).setComposition(composition);
float scale = DESIRED_WIDTH / (float) composition.getBounds().width();
((LottieAnimationView) child).setScale(scale);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ static AnimatableFloatValue newInstance(JSONObject json, LottieComposition compo

static AnimatableFloatValue newInstance(JSONObject json, LottieComposition composition,
boolean isDp) {
float scale = isDp ? composition.getScale() : 1f;
float scale = isDp ? composition.getDpScale() : 1f;
AnimatableValueParser.Result<Float> result = AnimatableValueParser
.newInstance(json, scale, composition, ValueFactory.INSTANCE)
.parseJson();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static AnimatableIntegerValue newInstance(LottieComposition composition, Integer

static AnimatableIntegerValue newInstance(JSONObject json, LottieComposition composition,
boolean isDp, boolean remap100To255) {
float scale = isDp ? composition.getScale() : 1f;
float scale = isDp ? composition.getDpScale() : 1f;
AnimatableValueParser.Result<Integer> result = AnimatableValueParser
.newInstance(json, scale, composition, ValueFactory.INSTANCE)
.parseJson();
Expand Down
20 changes: 14 additions & 6 deletions lottie/src/main/java/com/airbnb/lottie/AnimatableLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,13 @@ public void draw(@NonNull Canvas canvas) {
}
solidBackgroundPaint.setAlpha(alpha);
if (alpha > 0) {
canvas.drawRect(getBounds(), solidBackgroundPaint);
float scale = getLottieDrawable().getScale();
canvas.drawRect(
0,
0,
getBounds().width() * scale,
getBounds().height() * scale,
solidBackgroundPaint);
}
}
for (int i = 0; i < layers.size(); i++) {
Expand All @@ -118,24 +124,26 @@ void applyTransformForLayer(@Nullable Canvas canvas, AnimatableLayer layer) {
return;
}

float scale = getLottieDrawable().getScale();

PointF position = layer.transform.getPosition().getValue();
if (position.x != 0 || position.y != 0) {
canvas.translate(position.x, position.y);
canvas.translate(position.x * scale, position.y * scale);
}

float rotation = layer.transform.getRotation().getValue();
if (rotation != 0f) {
canvas.rotate(rotation);
}

ScaleXY scale = layer.transform.getScale().getValue();
if (scale.getScaleX() != 1f || scale.getScaleY() != 1f) {
canvas.scale(scale.getScaleX(), scale.getScaleY());
ScaleXY scaleTransform = layer.transform.getScale().getValue();
if (scaleTransform.getScaleX() != 1f || scaleTransform.getScaleY() != 1f) {
canvas.scale(scaleTransform.getScaleX(), scaleTransform.getScaleY());
}

PointF anchorPoint = layer.transform.getAnchorPoint().getValue();
if (anchorPoint.x != 0 || anchorPoint.y != 0) {
canvas.translate(-anchorPoint.x, -anchorPoint.y);
canvas.translate(-anchorPoint.x * scale, -anchorPoint.y * scale);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static IAnimatablePathValue createAnimatablePathOrSplitDimensionPath(
}
Keyframe.setEndFrames(keyframes);
} else {
initialPoint = JsonUtils.pointFromJsonArray((JSONArray) json, composition.getScale());
initialPoint = JsonUtils.pointFromJsonArray((JSONArray) json, composition.getDpScale());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private Factory() {

static AnimatablePointValue newInstance(JSONObject json, LottieComposition composition) {
AnimatableValueParser.Result<PointF> result = AnimatableValueParser
.newInstance(json, composition.getScale(), composition, PointFFactory.INSTANCE)
.newInstance(json, composition.getDpScale(), composition, PointFFactory.INSTANCE)
.parseJson();
return new AnimatablePointValue(result.keyframes, composition, result.initialValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private Factory() {

static AnimatableScaleValue newInstance(JSONObject json, LottieComposition
composition, boolean isDp) {
float scale = isDp ? composition.getScale() : 1f;
float scale = isDp ? composition.getDpScale() : 1f;
AnimatableValueParser.Result<ScaleXY> result = AnimatableValueParser
.newInstance(json, scale, composition, ScaleXY.Factory.INSTANCE)
.parseJson();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private Factory() {

static AnimatableShapeValue newInstance(JSONObject json, LottieComposition composition) {
AnimatableValueParser.Result<ShapeData> result = AnimatableValueParser
.newInstance(json, composition.getScale(), composition, ShapeData.Factory.INSTANCE)
.newInstance(json, composition.getDpScale(), composition, ShapeData.Factory.INSTANCE)
.parseJson();
return new AnimatableShapeValue(result.keyframes, composition, result.initialValue);
}
Expand Down
2 changes: 2 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/EllipseLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ class EllipseLayer extends AnimatableLayer {

if (fill != null) {
EllipseShapeLayer fillLayer = new EllipseShapeLayer(getCallback());
//noinspection ConstantConditions
fillLayer.setColor(fill.getColor().createAnimation());
fillLayer.setTransformOpacity(transform.getOpacity().createAnimation());
//noinspection ConstantConditions
fillLayer.setShapeOpacity(fill.getOpacity().createAnimation());
fillLayer.updateCircle(
circleShape.getPosition().createAnimation(),
Expand Down
8 changes: 4 additions & 4 deletions lottie/src/main/java/com/airbnb/lottie/Layer.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ static Layer newInstance(JSONObject json, LottieComposition composition) {
long parentId = json.optLong("parent", -1);

if (layerType == LayerType.Solid) {
solidWidth = (int) (json.optInt("sw") * composition.getScale());
solidHeight = (int) (json.optInt("sh") * composition.getScale());
solidWidth = (int) (json.optInt("sw") * composition.getDpScale());
solidHeight = (int) (json.optInt("sh") * composition.getDpScale());
solidColor = Color.parseColor(json.optString("sc"));
if (L.DBG) {
Log.d(TAG, "\tSolid=" + Integer.toHexString(solidColor) + " " +
Expand Down Expand Up @@ -243,8 +243,8 @@ static Layer newInstance(JSONObject json, LottieComposition composition) {
float startProgress = startFrame / frames;

if (layerType == LayerType.PreComp) {
preCompWidth = (int) (json.optInt("w") * composition.getScale());
preCompHeight = (int) (json.optInt("h") * composition.getScale());
preCompWidth = (int) (json.optInt("w") * composition.getDpScale());
preCompHeight = (int) (json.optInt("h") * composition.getDpScale());
}

float inFrame = json.optLong("ip");
Expand Down
13 changes: 10 additions & 3 deletions lottie/src/main/java/com/airbnb/lottie/LayerView.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,13 @@ void setMatteLayer(LayerView matteLayer) {
parent = parent.getParentLayer();
}

float scale = getLottieDrawable().getScale();
if (precompWidth != 0 || precompHeight != 0) {
canvas.clipRect(0, 0, precompWidth, precompHeight);
canvas.clipRect(0, 0, precompWidth * scale, precompHeight * scale);
} else {
canvas.clipRect(0, 0, composition.getBounds().width(), composition.getBounds().height());
canvas.clipRect(0, 0,
getLottieDrawable().getIntrinsicWidth(),
getLottieDrawable().getIntrinsicHeight());
}

if (!hasMasks() && !hasMatte()) {
Expand Down Expand Up @@ -298,6 +301,9 @@ private void applyMasks(Canvas canvas) {
}
applyTransformForLayer(canvas, this);

float scale = getLottieDrawable().getScale();
canvas.scale(scale, scale);

int size = mask.getMasks().size();
for (int i = 0; i < size; i++) {
Mask mask = this.mask.getMasks().get(i);
Expand All @@ -311,7 +317,7 @@ private void applyMasks(Canvas canvas) {
default:
maskPath.setFillType(Path.FillType.WINDING);
}
canvas.drawPath(maskAnimation.getValue(), mainCanvasPaint);
canvas.drawPath(maskPath, mainCanvasPaint);
}
canvas.restore();
}
Expand All @@ -328,6 +334,7 @@ private void drawImageIfNeeded(Canvas canvas) {

canvas.save();
applyTransformForLayer(canvas, this);
canvas.scale(getLottieDrawable().getScale(), getLottieDrawable().getScale());
imagePaint.setAlpha(getAlphaInternal());
canvas.drawBitmap(bitmap, 0, 0 ,imagePaint);
canvas.restore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,12 @@ public void resumeAnimation() {
lottieDrawable.setSpeed(speed);
}

void setScale(float scale) {
lottieDrawable.setScale(scale);
setImageDrawable(null);
setImageDrawable(lottieDrawable);
}

public void cancelAnimation() {
lottieDrawable.cancelAnimation();
}
Expand Down
12 changes: 6 additions & 6 deletions lottie/src/main/java/com/airbnb/lottie/LottieComposition.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ public class LottieComposition {
private final long startFrame;
private final long endFrame;
private final int frameRate;
private final float scale;
private final float dpScale;

private LottieComposition(Rect bounds, long startFrame, long endFrame, int frameRate,
float scale) {
private LottieComposition(
Rect bounds, long startFrame, long endFrame, int frameRate, float dpScale) {
this.bounds = bounds;
this.startFrame = startFrame;
this.endFrame = endFrame;
this.frameRate = frameRate;
this.scale = scale;
this.dpScale = dpScale;
}

Layer layerModelForId(long id) {
Expand Down Expand Up @@ -86,8 +86,8 @@ float getDurationFrames() {
}


public float getScale() {
return scale;
public float getDpScale() {
return dpScale;
}

@Override public String toString() {
Expand Down
48 changes: 29 additions & 19 deletions lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
Expand All @@ -29,6 +28,7 @@ public class LottieDrawable extends AnimatableLayer implements Drawable.Callback
private LottieComposition composition;
private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
private float speed = 1f;
private float scale = 1f;

@Nullable private ImageAssetBitmapManager imageAssetBitmapManager;
@Nullable private String imageAssetsFolder;
Expand Down Expand Up @@ -56,7 +56,7 @@ public class LottieDrawable extends AnimatableLayer implements Drawable.Callback
/**
* Returns whether or not any layers in this composition has masks.
*/
@SuppressWarnings("unused") public boolean hasMasks() {
@SuppressWarnings({"unused", "WeakerAccess"}) public boolean hasMasks() {
for (AnimatableLayer layer : layers) {
if (!(layer instanceof LayerView)) {
continue;
Expand All @@ -71,7 +71,7 @@ public class LottieDrawable extends AnimatableLayer implements Drawable.Callback
/**
* Returns whether or not any layers in this composition has a matte layer.
*/
@SuppressWarnings("unused") public boolean hasMatte() {
@SuppressWarnings({"unused", "WeakerAccess"}) public boolean hasMatte() {
for (AnimatableLayer layer : layers) {
if (!(layer instanceof LayerView)) {
continue;
Expand Down Expand Up @@ -103,7 +103,7 @@ public class LottieDrawable extends AnimatableLayer implements Drawable.Callback
/**
* If you have image assets and use {@link LottieDrawable} directly, you must call this yourself.
*
* Calling {@link #recycleBitmaps()} doesn't have to be final and {@link LottieDrawable}
* Calling recycleBitmaps() doesn't have to be final and {@link LottieDrawable}
* will recreate the bitmaps if needed but they will leak if you don't recycle them.
*
*/
Expand Down Expand Up @@ -132,7 +132,7 @@ public class LottieDrawable extends AnimatableLayer implements Drawable.Callback
clearComposition();
this.composition = composition;
setSpeed(speed);
setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height());
updateBounds();
buildLayersForComposition(composition);

setProgress(getProgress());
Expand Down Expand Up @@ -196,18 +196,10 @@ private void buildLayersForComposition(LottieComposition composition) {
if (composition == null) {
return;
}
Rect bounds = getBounds();
Rect compBounds = composition.getBounds();
int saveCount = canvas.save();
if (!bounds.equals(compBounds)) {
float scaleX = bounds.width() / (float) compBounds.width();
float scaleY = bounds.height() / (float) compBounds.height();
canvas.scale(scaleX, scaleY);
}
canvas.clipRect(getBounds());
canvas.clipRect(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
super.draw(canvas);
canvas.restoreToCount(saveCount);

}

void systemAnimationsAreDisabled() {
Expand All @@ -230,7 +222,7 @@ boolean isAnimating() {
playAnimation(false);
}

public void resumeAnimation() {
@SuppressWarnings("WeakerAccess") public void resumeAnimation() {
playAnimation(true);
}

Expand All @@ -246,7 +238,7 @@ private void playAnimation(boolean setStartTime) {
animator.start();
}

@SuppressWarnings("unused") public void resumeReverseAnimation() {
@SuppressWarnings({"unused", "WeakerAccess"}) public void resumeReverseAnimation() {
reverseAnimation(true);
}

Expand All @@ -266,7 +258,7 @@ private void reverseAnimation(boolean setStartTime) {
animator.reverse();
}

void setSpeed(float speed) {
@SuppressWarnings("WeakerAccess") public void setSpeed(float speed) {
this.speed = speed;
if (speed < 0) {
animator.setFloatValues(1f, 0f);
Expand All @@ -279,6 +271,24 @@ void setSpeed(float speed) {
}
}

@SuppressWarnings("WeakerAccess") public void setScale(float scale) {
this.scale = scale;
updateBounds();
invalidateSelf();
}

@SuppressWarnings("WeakerAccess") public float getScale() {
return scale;
}

private void updateBounds() {
if (composition == null) {
return;
}
setBounds(0, 0, (int) (composition.getBounds().width() * scale),
(int) (composition.getBounds().height() * scale));
}

void cancelAnimation() {
playAnimationWhenLayerAdded = false;
reverseAnimationWhenLayerAdded = false;
Expand Down Expand Up @@ -315,11 +325,11 @@ void removeAnimatorListener(Animator.AnimatorListener listener) {
}

@Override public int getIntrinsicWidth() {
return composition == null ? -1 : composition.getBounds().width();
return composition == null ? -1 : (int) (composition.getBounds().width() * scale);
}

@Override public int getIntrinsicHeight() {
return composition == null ? -1 : composition.getBounds().height();
return composition == null ? -1 : (int) (composition.getBounds().height() * scale);
}

Bitmap getImageAsset(String id) {
Expand Down
Loading

0 comments on commit c925806

Please sign in to comment.