Only update listeners when animation changed (#1035)

diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerFragment.kt
index cd7ad9f..bf47e6b 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerFragment.kt
@@ -254,6 +254,11 @@
             postInvalidate()
         }
 
+        animationView.setOnClickListener {
+            // Click the animation view to re-render it for debugging purposes.
+            animationView.invalidate()
+        }
+
         scaleSeekBar.setOnSeekBarChangeListener(OnSeekBarChangeListenerAdapter(
                 onProgressChanged = { _, progress, _ ->
                     val minScale = minScale()
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/BaseKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/BaseKeyframeAnimation.java
index 0a1c475..3dd38df 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/BaseKeyframeAnimation.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/BaseKeyframeAnimation.java
@@ -1,14 +1,14 @@
 package com.airbnb.lottie.animation.keyframe;
 
-import androidx.annotation.FloatRange;
-import androidx.annotation.Nullable;
-
-import com.airbnb.lottie.value.LottieValueCallback;
 import com.airbnb.lottie.value.Keyframe;
+import com.airbnb.lottie.value.LottieValueCallback;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.FloatRange;
+import androidx.annotation.Nullable;
+
 /**
  * @param <K> Keyframe type
  * @param <A> Animation type
@@ -28,6 +28,10 @@
 
   @Nullable private Keyframe<K> cachedKeyframe;
 
+  @Nullable private Keyframe<K> cachedGetValueKeyframe;
+  private float cachedGetValueProgress = -1f;
+  @Nullable private A cachedGetValue = null;
+
   BaseKeyframeAnimation(List<? extends Keyframe<K>> keyframes) {
     this.keyframes = keyframes;
   }
@@ -41,6 +45,9 @@
   }
 
   public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
+    // Must use hashCode() since the actual object instance will be returned
+    // from getValue() below with the new values.
+    int previousValue = valueCallback == null ? 0 : getValue().hashCode();
     if (progress < getStartDelayProgress()) {
       progress = getStartDelayProgress();
     } else if (progress > getEndProgress()) {
@@ -51,8 +58,12 @@
       return;
     }
     this.progress = progress;
+    // Just trigger a change but don't compute values if there is a value callback.
+    int newValue = valueCallback == null ? -1 : getValue().hashCode();
 
-    notifyListeners();
+    if (previousValue != newValue) {
+      notifyListeners();
+    }
   }
 
   public void notifyListeners() {
@@ -122,7 +133,18 @@
   }
 
   public A getValue() {
-    return getValue(getCurrentKeyframe(), getInterpolatedCurrentKeyframeProgress());
+    Keyframe<K> keyframe = getCurrentKeyframe();
+    float progress = getInterpolatedCurrentKeyframeProgress();
+    if (valueCallback == null && keyframe == cachedGetValueKeyframe && cachedGetValueProgress == progress) {
+      return cachedGetValue;
+    }
+
+    cachedGetValueKeyframe = keyframe;
+    cachedGetValueProgress = progress;
+    A value = getValue(keyframe, progress);
+    cachedGetValue = value;
+
+    return value;
   }
 
   public float getProgress() {