• 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   private 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     if (keyframe.isStatic()) {
105       return 0f;
106     }
107     //noinspection ConstantConditions
108     return keyframe.interpolator.getInterpolation(getLinearCurrentKeyframeProgress());
109   }
110 
111   @FloatRange(from = 0f, to = 1f)
getStartDelayProgress()112   private float getStartDelayProgress() {
113       if (cachedStartDelayProgress == -1f) {
114         cachedStartDelayProgress = keyframesWrapper.getStartDelayProgress();
115       }
116       return cachedStartDelayProgress;
117   }
118 
119   @FloatRange(from = 0f, to = 1f)
getEndProgress()120   float getEndProgress() {
121       if (cachedEndProgress == -1f) {
122         cachedEndProgress = keyframesWrapper.getEndProgress();
123       }
124       return cachedEndProgress;
125   }
126 
getValue()127   public A getValue() {
128     float progress = getInterpolatedCurrentKeyframeProgress();
129     if (valueCallback == null && keyframesWrapper.isCachedValueEnabled(progress)) {
130       return cachedGetValue;
131     }
132 
133     final Keyframe<K> keyframe = getCurrentKeyframe();
134     A value = getValue(keyframe, progress);
135     cachedGetValue = value;
136 
137     return value;
138   }
139 
getProgress()140   public float getProgress() {
141     return progress;
142   }
143 
setValueCallback(@ullable LottieValueCallback<A> valueCallback)144   public void setValueCallback(@Nullable LottieValueCallback<A> valueCallback) {
145     if (this.valueCallback != null) {
146       this.valueCallback.setAnimation(null);
147     }
148     this.valueCallback = valueCallback;
149     if (valueCallback != null) {
150       valueCallback.setAnimation(this);
151     }
152   }
153 
154   /**
155    * keyframeProgress will be [0, 1] unless the interpolator has overshoot in which case, this
156    * should be able to handle values outside of that range.
157    */
getValue(Keyframe<K> keyframe, float keyframeProgress)158   abstract A getValue(Keyframe<K> keyframe, float keyframeProgress);
159 
wrap(List<? extends Keyframe<T>> keyframes)160   private static <T> KeyframesWrapper<T> wrap(List<? extends Keyframe<T>> keyframes) {
161     if (keyframes.isEmpty()) {
162       return new EmptyKeyframeWrapper<>();
163     }
164     if (keyframes.size() == 1) {
165       return new SingleKeyframeWrapper<>(keyframes);
166     }
167     return new KeyframesWrapperImpl<>(keyframes);
168   }
169 
170   private interface KeyframesWrapper<T> {
isEmpty()171     boolean isEmpty();
172 
isValueChanged(float progress)173     boolean isValueChanged(float progress);
174 
getCurrentKeyframe()175     Keyframe<T> getCurrentKeyframe();
176 
177     @FloatRange(from = 0f, to = 1f)
getStartDelayProgress()178     float getStartDelayProgress();
179 
180     @FloatRange(from = 0f, to = 1f)
getEndProgress()181     float getEndProgress();
182 
isCachedValueEnabled(float interpolatedProgress)183     boolean isCachedValueEnabled(float interpolatedProgress);
184   }
185 
186   private static final class EmptyKeyframeWrapper<T> implements KeyframesWrapper<T> {
187     @Override
isEmpty()188     public boolean isEmpty() {
189       return true;
190     }
191 
192     @Override
isValueChanged(float progress)193     public boolean isValueChanged(float progress) {
194       return false;
195     }
196 
197     @Override
getCurrentKeyframe()198     public Keyframe<T> getCurrentKeyframe() {
199       throw new IllegalStateException("not implemented");
200     }
201 
202     @Override
getStartDelayProgress()203     public float getStartDelayProgress() {
204       return 0f;
205     }
206 
207     @Override
getEndProgress()208     public float getEndProgress() {
209       return 1f;
210     }
211 
212     @Override
isCachedValueEnabled(float interpolatedProgress)213     public boolean isCachedValueEnabled(float interpolatedProgress) {
214       throw new IllegalStateException("not implemented");
215     }
216   }
217 
218   private static final class SingleKeyframeWrapper<T> implements KeyframesWrapper<T> {
219     @NonNull
220     private final Keyframe<T> keyframe;
221     private float cachedInterpolatedProgress = -1f;
222 
SingleKeyframeWrapper(List<? extends Keyframe<T>> keyframes)223     SingleKeyframeWrapper(List<? extends Keyframe<T>> keyframes) {
224       this.keyframe = keyframes.get(0);
225     }
226 
227     @Override
isEmpty()228     public boolean isEmpty() {
229       return false;
230     }
231 
232     @Override
isValueChanged(float progress)233     public boolean isValueChanged(float progress) {
234       return !keyframe.isStatic();
235     }
236 
237     @Override
getCurrentKeyframe()238     public Keyframe<T> getCurrentKeyframe() {
239       return keyframe;
240     }
241 
242     @Override
getStartDelayProgress()243     public float getStartDelayProgress() {
244       return keyframe.getStartProgress();
245     }
246 
247     @Override
getEndProgress()248     public float getEndProgress() {
249       return keyframe.getEndProgress();
250     }
251 
252     @Override
isCachedValueEnabled(float interpolatedProgress)253     public boolean isCachedValueEnabled(float interpolatedProgress) {
254       if (cachedInterpolatedProgress == interpolatedProgress) {
255         return true;
256       }
257       cachedInterpolatedProgress = interpolatedProgress;
258       return false;
259     }
260   }
261 
262   private static final class KeyframesWrapperImpl<T> implements KeyframesWrapper<T> {
263     private final List<? extends Keyframe<T>> keyframes;
264     @NonNull
265     private Keyframe<T> currentKeyframe;
266     private Keyframe<T> cachedCurrentKeyframe = null;
267     private float cachedInterpolatedProgress = -1f;
268 
KeyframesWrapperImpl(List<? extends Keyframe<T>> keyframes)269     KeyframesWrapperImpl(List<? extends Keyframe<T>> keyframes) {
270       this.keyframes = keyframes;
271       currentKeyframe = findKeyframe(0);
272     }
273 
274     @Override
isEmpty()275     public boolean isEmpty() {
276       return false;
277     }
278 
279     @Override
isValueChanged(float progress)280     public boolean isValueChanged(float progress) {
281       if (currentKeyframe.containsProgress(progress)) {
282         return !currentKeyframe.isStatic();
283       }
284       currentKeyframe = findKeyframe(progress);
285       return true;
286     }
287 
findKeyframe(float progress)288     private Keyframe<T> findKeyframe(float progress) {
289       Keyframe<T> keyframe = keyframes.get(keyframes.size() - 1);
290       if (progress >= keyframe.getStartProgress()) {
291         return keyframe;
292       }
293       for (int i = keyframes.size() - 2; i >= 1; i--) {
294         keyframe = keyframes.get(i);
295         if (currentKeyframe == keyframe) {
296           continue;
297         }
298         if (keyframe.containsProgress(progress)) {
299           return keyframe;
300         }
301       }
302       return keyframes.get(0);
303     }
304 
305     @Override
306     @NonNull
getCurrentKeyframe()307     public Keyframe<T> getCurrentKeyframe() {
308       return currentKeyframe;
309     }
310 
311     @Override
getStartDelayProgress()312     public float getStartDelayProgress() {
313       return keyframes.get(0).getStartProgress();
314     }
315 
316     @Override
getEndProgress()317     public float getEndProgress() {
318       return keyframes.get(keyframes.size() - 1).getEndProgress();
319     }
320 
321     @Override
isCachedValueEnabled(float interpolatedProgress)322     public boolean isCachedValueEnabled(float interpolatedProgress) {
323       if (cachedCurrentKeyframe == currentKeyframe
324               && cachedInterpolatedProgress == interpolatedProgress) {
325         return true;
326       }
327       cachedCurrentKeyframe = currentKeyframe;
328       cachedInterpolatedProgress = interpolatedProgress;
329       return false;
330     }
331   }
332 }
333