From a9d3a1649c2d9d8f1d1d56311f0898b0b5502780 Mon Sep 17 00:00:00 2001 From: Erwan Ameil Date: Wed, 31 Jan 2024 20:45:52 +0000 Subject: [PATCH] Allow bounce easings along a path to return a position outside of the path (#2457) Don't clamp interpolated distance along path to 'progress' value between 0 and 1. Bounce in / Bounce out easings can transform the progress to negative values or values higher than 1. `getPosTan` unfortunately clamps the input to be between 0 and 1, so we need to handle the t < 0 and t > 1 cases separately. This is for the same issue I helped fix in the flutter library as well here: https://github.com/xvrh/lottie-flutter/pull/330 Expected result (both bounce-in and bounce out) along linear and curved paths: https://github.com/airbnb/lottie-android/assets/632735/6a25e139-fc81-4c3b-b3f3-be118c56db06 --- .../keyframe/PathKeyframeAnimation.java | 20 +- .../src/main/assets/Tests/BounceEasings.json | 898 ++++++++++++++++++ 2 files changed, 916 insertions(+), 2 deletions(-) create mode 100644 snapshot-tests/src/main/assets/Tests/BounceEasings.json diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframeAnimation.java index b4f1663610..7363f948b6 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframeAnimation.java @@ -11,6 +11,7 @@ public class PathKeyframeAnimation extends KeyframeAnimation { private final PointF point = new PointF(); private final float[] pos = new float[2]; + private final float[] tangent = new float[2]; private final PathMeasure pathMeasure = new PathMeasure(); private PathKeyframe pathMeasureKeyframe; @@ -39,8 +40,23 @@ pathKeyframe.startValue, pathKeyframe.endValue, getLinearCurrentKeyframeProgress pathMeasureKeyframe = pathKeyframe; } - pathMeasure.getPosTan(keyframeProgress * pathMeasure.getLength(), pos, null); - point.set(pos[0], pos[1]); + // allow bounce easings to calculate positions outside the path + // by using the tangent at the extremities + + float length = pathMeasure.getLength(); + + if (keyframeProgress < 0) { + pathMeasure.getPosTan(0, pos, tangent); + float tangentAmount = keyframeProgress * length; + point.set(pos[0] + tangent[0] * tangentAmount, pos[1] + tangent[1] * tangentAmount); + } else if (keyframeProgress > 1) { + pathMeasure.getPosTan(length, pos, tangent); + float tangentAmount = (keyframeProgress - 1) * length; + point.set(pos[0] + tangent[0] * tangentAmount, pos[1] + tangent[1] * tangentAmount); + } else { + pathMeasure.getPosTan(keyframeProgress * length, pos, null); + point.set(pos[0], pos[1]); + } return point; } } diff --git a/snapshot-tests/src/main/assets/Tests/BounceEasings.json b/snapshot-tests/src/main/assets/Tests/BounceEasings.json new file mode 100644 index 0000000000..6cd663d46f --- /dev/null +++ b/snapshot-tests/src/main/assets/Tests/BounceEasings.json @@ -0,0 +1,898 @@ +{ + "v": "5.7.5", + "fr": 100, + "ip": 0, + "op": 400, + "w": 800, + "h": 1000, + "nm": "Comp 1", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 0, + "ty": 4, + "nm": "Bounce out curve", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + } + }, + "ao": 0, + "hd": false, + "shapes": [ + { + "ty": "gr", + "hd": false, + "it": [ + { + "ty": "rc", + "hd": false, + "d": 1, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.21176470588235294, + 0.9176470588235294, + 1 + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 2 + }, + "e": { + "a": 0, + "k": 100, + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 2 + }, + "m": 1, + "hd": false + }, + { + "ty": "tr", + "hd": false, + "p": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 510, + 100 + ], + "i": { + "x": [ + 0.2 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.5 + ], + "y": [ + -0.5 + ] + }, + "ti": [ + -268.19047619047615, + -294.42857142857247 + ], + "to": [ + -268.19047619047615, + 238.90476190476068 + ] + }, + { + "t": 400, + "s": [ + 510, + 900 + ], + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + } + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 2 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 2 + } + } + ] + } + ], + "ip": 0, + "op": 401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Bounce in curve", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + } + }, + "ao": 0, + "hd": false, + "shapes": [ + { + "ty": "gr", + "hd": false, + "it": [ + { + "ty": "rc", + "hd": false, + "d": 1, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.3411764705882353, + 0.21176470588235294 + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 2 + }, + "e": { + "a": 0, + "k": 100, + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 2 + }, + "m": 1, + "hd": false + }, + { + "ty": "tr", + "hd": false, + "p": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 360, + 100 + ], + "i": { + "x": [ + 0.5 + ], + "y": [ + 1.5 + ] + }, + "o": { + "x": [ + 0.8 + ], + "y": [ + 0 + ] + }, + "ti": [ + -268.19047619047615, + -294.42857142857247 + ], + "to": [ + -268.19047619047615, + 238.90476190476068 + ] + }, + { + "t": 400, + "s": [ + 360, + 900 + ], + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + } + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 2 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 2 + } + } + ] + } + ], + "ip": 0, + "op": 401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Bounce in", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + } + }, + "ao": 0, + "hd": false, + "shapes": [ + { + "ty": "gr", + "hd": false, + "it": [ + { + "ty": "rc", + "hd": false, + "d": 1, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.7254901960784313, + 0.5764705882352941 + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 2 + }, + "e": { + "a": 0, + "k": 100, + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 2 + }, + "m": 1, + "hd": false + }, + { + "ty": "tr", + "hd": false, + "p": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 650, + 100 + ], + "i": { + "x": [ + 0.5 + ], + "y": [ + 1.5 + ] + }, + "o": { + "x": [ + 0.8 + ], + "y": [ + 0 + ] + } + }, + { + "t": 400, + "s": [ + 650, + 900 + ], + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + } + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 2 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 2 + } + } + ] + } + ], + "ip": 0, + "op": 401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Bounce out", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + } + }, + "ao": 0, + "hd": false, + "shapes": [ + { + "ty": "gr", + "hd": false, + "it": [ + { + "ty": "rc", + "hd": false, + "d": 1, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.8392156862745098, + 0.25098039215686274 + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 2 + }, + "e": { + "a": 0, + "k": 100, + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 2 + }, + "m": 1, + "hd": false + }, + { + "ty": "tr", + "hd": false, + "p": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 150, + 100 + ], + "i": { + "x": [ + 0.2 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.5 + ], + "y": [ + -0.5 + ] + } + }, + { + "t": 400, + "s": [ + 150, + 900 + ], + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + } + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 2 + }, + "o": { + "a": 0, + "k": 100, + "ix": 2 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 2 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 2 + } + } + ] + } + ], + "ip": 0, + "op": 401, + "st": 0, + "bm": 0 + } + ], + "markers": [] +}