public <T> void addValueCallback(
      final KeyPath keyPath, final T property, @Nullable final LottieValueCallback<T> callback) {
    if (compositionLayer == null) {
      lazyCompositionTasks.add(c -> addValueCallback(keyPath, property, callback));
      return;
    }
    boolean invalidate;
    if (keyPath == KeyPath.COMPOSITION) {
      compositionLayer.addValueCallback(property, callback);
      invalidate = true;
    } else if (keyPath.getResolvedElement() != null) {
      keyPath.getResolvedElement().addValueCallback(property, callback);
      invalidate = true;
    } else {
      List<KeyPath> elements = resolveKeyPath(keyPath);

      for (int i = 0; i < elements.size(); i++) {
        //noinspection ConstantConditions
        elements.get(i).getResolvedElement().addValueCallback(property, callback);
      }
      invalidate = !elements.isEmpty();
    }
    if (invalidate) {
      invalidateSelf();
      if (property == LottieProperty.TIME_REMAP) {
        // Time remapping values are read in setProgress. In order for the new value
        // to apply, we have to re-set the progress with the current progress so that the
        // time remapping can be reapplied.
        setProgress(getProgress());
      }
    }
  }
Add an property callback for the specified {@link KeyPath}. This {@link KeyPath} can resolve
to multiple contents. In that case, the callback's value will apply to all of them.
<p>
Internally, this will check if the {@link KeyPath} has already been resolved with
{@link #resolveKeyPath(KeyPath)} and will resolve it if it hasn't.
<p>
Set the callback to null to clear it.
lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java