content: package com.airbnb.lottie;
  
  import android.support.annotation.FloatRange;
  import android.support.annotation.Nullable;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * @param <K> Keyframe type
   * @param <A> Animation type
   */
  abstract class BaseKeyframeAnimation<K, A> {
    interface AnimationListener {
      void onValueChanged();
    }
  
    final List<AnimationListener> listeners = new ArrayList<>();
    private boolean isDiscrete = false;
  
    private final List<? extends Keyframe<K>> keyframes;
    private float progress = 0f;
  
    @Nullable private Keyframe<K> cachedKeyframe;
  
    BaseKeyframeAnimation(List<? extends Keyframe<K>> keyframes) {
      this.keyframes = keyframes;
    }
  
    void setIsDiscrete() {
      isDiscrete = true;
    }
  
    void addUpdateListener(AnimationListener listener) {
      listeners.add(listener);
    }
  
    void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
      if (progress < getStartDelayProgress()) {
        progress = 0f;
      } else if (progress > getEndProgress()) {
        progress = 1f;
      }
  
      if (progress == this.progress) {
        return;
      }
      this.progress = progress;
  
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onValueChanged();
      }
    }
  
    private Keyframe<K> getCurrentKeyframe() {
      if (keyframes.isEmpty()) {
        throw new IllegalStateException("There are no keyframes");
      }
  
      if (cachedKeyframe != null && cachedKeyframe.containsProgress(progress)) {
        return cachedKeyframe;
      }
  
      int i = 0;
      Keyframe<K> keyframe = keyframes.get(0);
      if (progress < keyframe.getStartProgress()) {
        cachedKeyframe = keyframe;
        return keyframe;
      }
  
      while (!keyframe.containsProgress(progress) && i < keyframes.size()) {
        keyframe = keyframes.get(i);
        i++;
      }
      cachedKeyframe = keyframe;
      return keyframe;
    }
  
    /**
     * This wil be [0, 1] unless the interpolator has overshoot in which case getValue() should be
     * able to handle values outside of that range.
     */
    private float getCurrentKeyframeProgress() {
      if (isDiscrete) {
        return 0f;
      }
  
      Keyframe<K> keyframe = getCurrentKeyframe();
      if (keyframe.isStatic()) {
        return 0f;
      }
      float progressIntoFrame = progress - keyframe.getStartProgress();
      float keyframeProgress = keyframe.getEndProgress() - keyframe.getStartProgress();
      //noinspection ConstantConditions
      return keyframe.interpolator.getInterpolation(progressIntoFrame / keyframeProgress);
    }
  
    @FloatRange(from = 0f, to = 1f)
    private float getStartDelayProgress() {
      return keyframes.isEmpty() ? 0f : keyframes.get(0).getStartProgress();
    }
  
    @FloatRange(from = 0f, to = 1f)
    private float getEndProgress() {
      return keyframes.isEmpty() ? 1f : keyframes.get(keyframes.size() - 1).getEndProgress();
    }
  
    public A getValue() {
      return getValue(getCurrentKeyframe(), getCurrentKeyframeProgress());
    }
  
    /**
     * keyframeProgress will be [0, 1] unless the interpolator has overshoot in which case, this
     * should be able to handle values outside of that range.
     */
    abstract A getValue(Keyframe<K> keyframe, float keyframeProgress);
  }
  