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