• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.airbnb.lottie.animation.keyframe;
2 
3 import androidx.annotation.FloatRange;
4 import androidx.annotation.NonNull;
5 import androidx.annotation.Nullable;
6 
7 import com.airbnb.lottie.L;
8 import com.airbnb.lottie.value.Keyframe;
9 import com.airbnb.lottie.value.LottieValueCallback;
10 
11 import java.util.ArrayList;
12 import java.util.List;
13 
14 /**
15  * @param <K> Keyframe type
16  * @param <A> Animation type
17  */
18 public abstract class BaseKeyframeAnimation<K, A> {
19   public interface AnimationListener {
onValueChanged()20     void onValueChanged();
21   }
22 
23   // This is not a Set because we don't want to create an iterator object on every setProgress.
24   final List<AnimationListener> listeners = new ArrayList<>(1);
25   private boolean isDiscrete = false;
26 
27   private final KeyframesWrapper<K> keyframesWrapper;
28   protected float progress = 0f;
29   @Nullable protected LottieValueCallback<A> valueCallback;
30 
31   @Nullable private A cachedGetValue = null;
32 
33   private float cachedStartDelayProgress = -1f;
34   private float cachedEndProgress = -1f;
35 
BaseKeyframeAnimation(List<? extends Keyframe<K>> keyframes)36   BaseKeyframeAnimation(List<? extends Keyframe<K>> keyframes) {
37     keyframesWrapper = wrap(keyframes);
38   }
39 
setIsDiscrete()40   public void setIsDiscrete() {
41     isDiscrete = true;
42   }
43 
addUpdateListener(AnimationListener listener)44   public void addUpdateListener(AnimationListener listener) {
45     listeners.add(listener);
46   }
47 
setProgress(@loatRangefrom = 0f, to = 1f) float progress)48   public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
49     if (keyframesWrapper.isEmpty()) {
50       return;
51     }
52     if (progress < getStartDelayProgress()) {
53       progress = getStartDelayProgress();
54     } else if (progress > getEndProgress()) {
55       progress = getEndProgress();
56     }
57 
58     if (progress == this.progress) {
59       return;
60     }
61     this.progress = progress;
62     if (keyframesWrapper.isValueChanged(progress)) {
63       notifyListeners();
64     }
65   }
66 
notifyListeners()67   public void notifyListeners() {
68     for (int i = 0; i < listeners.size(); i++) {
69       listeners.get(i).onValueChanged();
70     }
71   }
72 
getCurrentKeyframe()73   protected Keyframe<K> getCurrentKeyframe() {
74     L.beginSection("BaseKeyframeAnimation#getCurrentKeyframe");
75     final Keyframe<K> keyframe = keyframesWrapper.getCurrentKeyframe();
76     L.endSection("BaseKeyframeAnimation#getCurrentKeyframe");
77     return keyframe;
78   }
79 
80   /**
81    * Returns the progress into the current keyframe between 0 and 1. This does not take into account
82    * any interpolation that the keyframe may have.
83    */
getLinearCurrentKeyframeProgress()84   float getLinearCurrentKeyframeProgress() {
85     if (isDiscrete) {
86       return 0f;
87     }
88 
89     Keyframe<K> keyframe = getCurrentKeyframe();
90     if (keyframe.isStatic()) {
91       return 0f;
92     }
93     float progressIntoFrame = progress - keyframe.getStartProgress();
94     float keyframeProgress = keyframe.getEndProgress() - keyframe.getStartProgress();
95     return progressIntoFrame / keyframeProgress;
96   }
97 
98   /**
99    * Takes the value of {@link #getLinearCurrentKeyframeProgress()} and interpolates it with
100    * the current keyframe's interpolator.
101    */
getInterpolatedCurrentKeyframeProgress()102   protected float getInterpolatedCurrentKeyframeProgress() {
103     Keyframe<K> keyframe = getCurrentKeyframe();
104     // Keyframe should not be null here but there seems to be a Xiaomi Android 10 specific crash.
105     // https://github.com/airbnb/lottie-android/issues/2050
106     if (keyframe == null || keyframe.isStatic()) {
107       return 0f;
108     }
109     //noinspection ConstantConditions
110     return keyframe.interpolator.getInterpolation(getLinearCurrentKeyframeProgress());
111   }
112 
113   @FloatRange(from = 0f, to = 1f)
getStartDelayProgress()114   private float getStartDelayProgress() {
115     if (cachedStartDelayProgress == -1f) {
116       cachedStartDelayProgress = keyframesWrapper.getStartDelayProgress();
117     }
118     return cachedStartDelayProgress;
119   }
120 
121   @FloatRange(from = 0f, to = 1f)
getEndProgress()122   float getEndProgress() {
123     if (cachedEndProgress == -1f) {
124       cachedEndProgress = keyframesWrapper.getEndProgress();
125     }
126     return cachedEndProgress;
127   }
128 
getValue()129   public A getValue() {
130     A value;
131 
132     float linearProgress = getLinearCurrentKeyframeProgress();
133     if (valueCallback == null && keyframesWrapper.isCachedValueEnabled(linearProgress)) {
134       return cachedGetValue;
135     }
136     final Keyframe<K> keyframe = getCurrentKeyframe();
137 
138     if (keyframe.xInterpolator != null && keyframe.yInterpolator != null) {
139       float xProgress = keyframe.xInterpolator.getInterpolation(linearProgress);
140       float yProgress = keyframe.yInterpolator.getInterpolation(linearProgress);
141       value = getValue(keyframe, linearProgress, xProgress, yProgress);
142     } else {
143       float progress = getInterpolatedCurrentKeyframeProgress();
144       value = getValue(keyframe, progress);
145     }
146 
147     cachedGetValue = value;
148     return value;
149   }
150 
getProgress()151   public float getProgress() {
152     return progress;
153   }
154 
setValueCallback(@ullable LottieValueCallback<A> valueCallback)155   public void setValueCallback(@Nullable LottieValueCallback<A> valueCallback) {
156     if (this.valueCallback != null) {
157       this.valueCallback.setAnimation(null);
158     }
159     this.valueCallback = valueCallback;
160     if (valueCallback != null) {
161       valueCallback.setAnimation(this);
162     }
163   }
164 
165   /**
166    * keyframeProgress will be [0, 1] unless the interpolator has overshoot in which case, this
167    * should be able to handle values outside of that range.
168    */
getValue(Keyframe<K> keyframe, float keyframeProgress)169   abstract A getValue(Keyframe<K> keyframe, float keyframeProgress);
170 
171   /**
172    * Similar to {@link #getValue(Keyframe, float)} but used when an animation has separate interpolators for the X and Y axis.
173    */
getValue(Keyframe<K> keyframe, float linearKeyframeProgress, float xKeyframeProgress, float yKeyframeProgress)174   protected A getValue(Keyframe<K> keyframe, float linearKeyframeProgress, float xKeyframeProgress, float yKeyframeProgress) {
175     throw new UnsupportedOperationException("This animation does not support split dimensions!");
176   }
177 
wrap(List<? extends Keyframe<T>> keyframes)178   private static <T> KeyframesWrapper<T> wrap(List<? extends Keyframe<T>> keyframes) {
179     if (keyframes.isEmpty()) {
180       return new EmptyKeyframeWrapper<>();
181     }
182     if (keyframes.size() == 1) {
183       return new SingleKeyframeWrapper<>(keyframes);
184     }
185     return new KeyframesWrapperImpl<>(keyframes);
186   }
187 
188   private interface KeyframesWrapper<T> {
isEmpty()189     boolean isEmpty();
190 
isValueChanged(float progress)191     boolean isValueChanged(float progress);
192 
getCurrentKeyframe()193     Keyframe<T> getCurrentKeyframe();
194 
195     @FloatRange(from = 0f, to = 1f)
getStartDelayProgress()196     float getStartDelayProgress();
197 
198     @FloatRange(from = 0f, to = 1f)
getEndProgress()199     float getEndProgress();
200 
isCachedValueEnabled(float progress)201     boolean isCachedValueEnabled(float progress);
202   }
203 
204   private static final class EmptyKeyframeWrapper<T> implements KeyframesWrapper<T> {
205     @Override
isEmpty()206     public boolean isEmpty() {
207       return true;
208     }
209 
210     @Override
isValueChanged(float progress)211     public boolean isValueChanged(float progress) {
212       return false;
213     }
214 
215     @Override
getCurrentKeyframe()216     public Keyframe<T> getCurrentKeyframe() {
217       throw new IllegalStateException("not implemented");
218     }
219 
220     @Override
getStartDelayProgress()221     public float getStartDelayProgress() {
222       return 0f;
223     }
224 
225     @Override
getEndProgress()226     public float getEndProgress() {
227       return 1f;
228     }
229 
230     @Override
isCachedValueEnabled(float progress)231     public boolean isCachedValueEnabled(float progress) {
232       throw new IllegalStateException("not implemented");
233     }
234   }
235 
236   private static final class SingleKeyframeWrapper<T> implements KeyframesWrapper<T> {
237     @NonNull
238     private final Keyframe<T> keyframe;
239     private float cachedInterpolatedProgress = -1f;
240 
SingleKeyframeWrapper(List<? extends Keyframe<T>> keyframes)241     SingleKeyframeWrapper(List<? extends Keyframe<T>> keyframes) {
242       this.keyframe = keyframes.get(0);
243     }
244 
245     @Override
isEmpty()246     public boolean isEmpty() {
247       return false;
248     }
249 
250     @Override
isValueChanged(float progress)251     public boolean isValueChanged(float progress) {
252       return !keyframe.isStatic();
253     }
254 
255     @Override
getCurrentKeyframe()256     public Keyframe<T> getCurrentKeyframe() {
257       return keyframe;
258     }
259 
260     @Override
getStartDelayProgress()261     public float getStartDelayProgress() {
262       return keyframe.getStartProgress();
263     }
264 
265     @Override
getEndProgress()266     public float getEndProgress() {
267       return keyframe.getEndProgress();
268     }
269 
270     @Override
isCachedValueEnabled(float progress)271     public boolean isCachedValueEnabled(float progress) {
272       if (cachedInterpolatedProgress == progress) {
273         return true;
274       }
275       cachedInterpolatedProgress = progress;
276       return false;
277     }
278   }
279 
280   private static final class KeyframesWrapperImpl<T> implements KeyframesWrapper<T> {
281     private final List<? extends Keyframe<T>> keyframes;
282     @NonNull
283     private Keyframe<T> currentKeyframe;
284     private Keyframe<T> cachedCurrentKeyframe = null;
285     private float cachedInterpolatedProgress = -1f;
286 
KeyframesWrapperImpl(List<? extends Keyframe<T>> keyframes)287     KeyframesWrapperImpl(List<? extends Keyframe<T>> keyframes) {
288       this.keyframes = keyframes;
289       currentKeyframe = findKeyframe(0);
290     }
291 
292     @Override
isEmpty()293     public boolean isEmpty() {
294       return false;
295     }
296 
297     @Override
isValueChanged(float progress)298     public boolean isValueChanged(float progress) {
299       if (currentKeyframe.containsProgress(progress)) {
300         return !currentKeyframe.isStatic();
301       }
302       currentKeyframe = findKeyframe(progress);
303       return true;
304     }
305 
findKeyframe(float progress)306     private Keyframe<T> findKeyframe(float progress) {
307       Keyframe<T> keyframe = keyframes.get(keyframes.size() - 1);
308       if (progress >= keyframe.getStartProgress()) {
309         return keyframe;
310       }
311       for (int i = keyframes.size() - 2; i >= 1; i--) {
312         keyframe = keyframes.get(i);
313         if (currentKeyframe == keyframe) {
314           continue;
315         }
316         if (keyframe.containsProgress(progress)) {
317           return keyframe;
318         }
319       }
320       return keyframes.get(0);
321     }
322 
323     @Override
324     @NonNull
getCurrentKeyframe()325     public Keyframe<T> getCurrentKeyframe() {
326       return currentKeyframe;
327     }
328 
329     @Override
getStartDelayProgress()330     public float getStartDelayProgress() {
331       return keyframes.get(0).getStartProgress();
332     }
333 
334     @Override
getEndProgress()335     public float getEndProgress() {
336       return keyframes.get(keyframes.size() - 1).getEndProgress();
337     }
338 
339     @Override
isCachedValueEnabled(float progress)340     public boolean isCachedValueEnabled(float progress) {
341       if (cachedCurrentKeyframe == currentKeyframe
342           && cachedInterpolatedProgress == progress) {
343         return true;
344       }
345       cachedCurrentKeyframe = currentKeyframe;
346       cachedInterpolatedProgress = progress;
347       return false;
348     }
349   }
350 }
351