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