content: package com.airbnb.lottie.animation.content;
  
  import android.graphics.Canvas;
  import android.graphics.LinearGradient;
  import android.graphics.Matrix;
  import android.graphics.PointF;
  import android.graphics.RadialGradient;
  import android.graphics.RectF;
  import android.graphics.Shader;
  
  import androidx.annotation.Nullable;
  import androidx.collection.LongSparseArray;
  
  import com.airbnb.lottie.LottieDrawable;
  import com.airbnb.lottie.LottieProperty;
  import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
  import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation;
  import com.airbnb.lottie.model.content.GradientColor;
  import com.airbnb.lottie.model.content.GradientStroke;
  import com.airbnb.lottie.model.content.GradientType;
  import com.airbnb.lottie.model.layer.BaseLayer;
  import com.airbnb.lottie.utils.DropShadow;
  import com.airbnb.lottie.value.LottieValueCallback;
  
  public class GradientStrokeContent extends BaseStrokeContent {
    /**
     * Cache the gradients such that it runs at 30fps.
     */
    private static final int CACHE_STEPS_MS = 32;
  
    private final String name;
    private final boolean hidden;
    private final LongSparseArray<LinearGradient> linearGradientCache = new LongSparseArray<>();
    private final LongSparseArray<RadialGradient> radialGradientCache = new LongSparseArray<>();
    private final RectF boundsRect = new RectF();
  
    private final GradientType type;
    private final int cacheSteps;
    private final BaseKeyframeAnimation<GradientColor, GradientColor> colorAnimation;
    private final BaseKeyframeAnimation<PointF, PointF> startPointAnimation;
    private final BaseKeyframeAnimation<PointF, PointF> endPointAnimation;
    @Nullable private ValueCallbackKeyframeAnimation colorCallbackAnimation;
  
    public GradientStrokeContent(
        final LottieDrawable lottieDrawable, BaseLayer layer, GradientStroke stroke) {
      super(lottieDrawable, layer, stroke.getCapType().toPaintCap(),
          stroke.getJoinType().toPaintJoin(), stroke.getMiterLimit(), stroke.getOpacity(),
          stroke.getWidth(), stroke.getLineDashPattern(), stroke.getDashOffset());
  
      name = stroke.getName();
      type = stroke.getGradientType();
      hidden = stroke.isHidden();
      cacheSteps = (int) (lottieDrawable.getComposition().getDuration() / CACHE_STEPS_MS);
  
      colorAnimation = stroke.getGradientColor().createAnimation();
      colorAnimation.addUpdateListener(this);
      layer.addAnimation(colorAnimation);
  
      startPointAnimation = stroke.getStartPoint().createAnimation();
      startPointAnimation.addUpdateListener(this);
      layer.addAnimation(startPointAnimation);
  
      endPointAnimation = stroke.getEndPoint().createAnimation();
      endPointAnimation.addUpdateListener(this);
      layer.addAnimation(endPointAnimation);
    }
  
    @Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, DropShadow shadowToApply) {
      if (hidden) {
        return;
      }
      getBounds(boundsRect, parentMatrix, false);
  
      Shader shader;
      if (type == GradientType.LINEAR) {
        shader = getLinearGradient();
      } else {
        shader = getRadialGradient();
      }
      paint.setShader(shader);
  
      super.draw(canvas, parentMatrix, parentAlpha, shadowToApply);
    }
  
    @Override public String getName() {
      return name;
    }
  
    private LinearGradient getLinearGradient() {
      int gradientHash = getGradientHash();
      LinearGradient gradient = linearGradientCache.get(gradientHash);
      if (gradient != null) {
        return gradient;
      }
      PointF startPoint = startPointAnimation.getValue();
      PointF endPoint = endPointAnimation.getValue();
      GradientColor gradientColor = colorAnimation.getValue();
      int[] colors = applyDynamicColorsIfNeeded(gradientColor.getColors());
      float[] positions = gradientColor.getPositions();
      float x0 = startPoint.x;
      float y0 = startPoint.y;
      float x1 = endPoint.x;
      float y1 = endPoint.y;
      gradient = new LinearGradient(x0, y0, x1, y1, colors, positions, Shader.TileMode.CLAMP);
      linearGradientCache.put(gradientHash, gradient);
      return gradient;
    }
  
    private RadialGradient getRadialGradient() {
      int gradientHash = getGradientHash();
      RadialGradient gradient = radialGradientCache.get(gradientHash);
      if (gradient != null) {
        return gradient;
      }
      PointF startPoint = startPointAnimation.getValue();
      PointF endPoint = endPointAnimation.getValue();
      GradientColor gradientColor = colorAnimation.getValue();
      int[] colors = applyDynamicColorsIfNeeded(gradientColor.getColors());
      float[] positions = gradientColor.getPositions();
      float x0 = startPoint.x;
      float y0 = startPoint.y;
      float x1 = endPoint.x;
      float y1 = endPoint.y;
      float r = (float) Math.hypot(x1 - x0, y1 - y0);
      gradient = new RadialGradient(x0, y0, r, colors, positions, Shader.TileMode.CLAMP);
      radialGradientCache.put(gradientHash, gradient);
      return gradient;
    }
  
    private int getGradientHash() {
      int startPointProgress = Math.round(startPointAnimation.getProgress() * cacheSteps);
      int endPointProgress = Math.round(endPointAnimation.getProgress() * cacheSteps);
      int colorProgress = Math.round(colorAnimation.getProgress() * cacheSteps);
      int hash = 17;
      if (startPointProgress != 0) {
        hash = hash * 31 * startPointProgress;
      }
      if (endPointProgress != 0) {
        hash = hash * 31 * endPointProgress;
      }
      if (colorProgress != 0) {
        hash = hash * 31 * colorProgress;
      }
      return hash;
    }
  
    private int[] applyDynamicColorsIfNeeded(int[] colors) {
      if (colorCallbackAnimation != null) {
        Integer[] dynamicColors = (Integer[]) colorCallbackAnimation.getValue();
        if (colors.length == dynamicColors.length) {
          for (int i = 0; i < colors.length; i++) {
            colors[i] = dynamicColors[i];
          }
        } else {
          colors = new int[dynamicColors.length];
          for (int i = 0; i < dynamicColors.length; i++) {
            colors[i] = dynamicColors[i];
          }
        }
      }
      return colors;
    }
  
    @Override
    public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
      super.addValueCallback(property, callback);
      if (property == LottieProperty.GRADIENT_COLOR) {
        if (colorCallbackAnimation != null) {
          layer.removeAnimation(colorCallbackAnimation);
        }
  
        if (callback == null) {
          colorCallbackAnimation = null;
        } else {
          colorCallbackAnimation = new ValueCallbackKeyframeAnimation<>(callback);
          colorCallbackAnimation.addUpdateListener(this);
          layer.addAnimation(colorCallbackAnimation);
        }
      }
    }
  }
  