content: package com.airbnb.lottie.animation.content;
  
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.graphics.Path;
  import android.graphics.RectF;
  
  import androidx.annotation.Nullable;
  
  import com.airbnb.lottie.LottieDrawable;
  import com.airbnb.lottie.LottieProperty;
  import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
  import com.airbnb.lottie.animation.keyframe.TransformKeyframeAnimation;
  import com.airbnb.lottie.model.KeyPath;
  import com.airbnb.lottie.model.content.Repeater;
  import com.airbnb.lottie.model.layer.BaseLayer;
  import com.airbnb.lottie.utils.DropShadow;
  import com.airbnb.lottie.utils.MiscUtils;
  import com.airbnb.lottie.value.LottieValueCallback;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.ListIterator;
  
  public class RepeaterContent implements DrawingContent, PathContent, GreedyContent,
      BaseKeyframeAnimation.AnimationListener, KeyPathElementContent {
    private final Matrix matrix = new Matrix();
    private final Path path = new Path();
  
    private final LottieDrawable lottieDrawable;
    private final BaseLayer layer;
    private final String name;
    private final boolean hidden;
    private final BaseKeyframeAnimation<Float, Float> copies;
    private final BaseKeyframeAnimation<Float, Float> offset;
    private final TransformKeyframeAnimation transform;
    private ContentGroup contentGroup;
  
  
    public RepeaterContent(LottieDrawable lottieDrawable, BaseLayer layer, Repeater repeater) {
      this.lottieDrawable = lottieDrawable;
      this.layer = layer;
      name = repeater.getName();
      this.hidden = repeater.isHidden();
      copies = repeater.getCopies().createAnimation();
      layer.addAnimation(copies);
      copies.addUpdateListener(this);
  
      offset = repeater.getOffset().createAnimation();
      layer.addAnimation(offset);
      offset.addUpdateListener(this);
  
      transform = repeater.getTransform().createAnimation();
      transform.addAnimationsToLayer(layer);
      transform.addListener(this);
    }
  
    @Override public void absorbContent(ListIterator<Content> contentsIter) {
      // This check prevents a repeater from getting added twice.
      // This can happen in the following situation:
      //    RECTANGLE
      //    REPEATER 1
      //    FILL
      //    REPEATER 2
      // In this case, the expected structure would be:
      //     REPEATER 2
      //        REPEATER 1
      //            RECTANGLE
      //        FILL
      // Without this check, REPEATER 1 will try and absorb contents once it is already inside of
      // REPEATER 2.
      if (contentGroup != null) {
        return;
      }
      // Fast forward the iterator until after this content.
      //noinspection StatementWithEmptyBody
      while (contentsIter.hasPrevious() && contentsIter.previous() != this) {
      }
      List<Content> contents = new ArrayList<>();
      while (contentsIter.hasPrevious()) {
        contents.add(contentsIter.previous());
        contentsIter.remove();
      }
      Collections.reverse(contents);
      contentGroup = new ContentGroup(lottieDrawable, layer, "Repeater", hidden, contents, null);
    }
  
    @Override public String getName() {
      return name;
    }
  
    @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
      contentGroup.setContents(contentsBefore, contentsAfter);
    }
  
    @Override public Path getPath() {
      Path contentPath = contentGroup.getPath();
      path.reset();
      float copies = this.copies.getValue();
      float offset = this.offset.getValue();
      for (int i = (int) copies - 1; i >= 0; i--) {
        matrix.set(transform.getMatrixForRepeater(i + offset));
        path.addPath(contentPath, matrix);
      }
      return path;
    }
  
    @Override public void draw(Canvas canvas, Matrix parentMatrix, int alpha, @Nullable DropShadow shadowToApply) {
      float copies = this.copies.getValue();
      float offset = this.offset.getValue();
      //noinspection ConstantConditions
      float startOpacity = this.transform.getStartOpacity().getValue() / 100f;
      //noinspection ConstantConditions
      float endOpacity = this.transform.getEndOpacity().getValue() / 100f;
      for (int i = (int) copies - 1; i >= 0; i--) {
        matrix.set(parentMatrix);
        matrix.preConcat(transform.getMatrixForRepeater(i + offset));
        float newAlpha = alpha * MiscUtils.lerp(startOpacity, endOpacity, i / copies);
        // A repeater renders its contents as if by simple re-rendering, so it should be fine to pass shadowToApply
        // here, even when we have more than 1 copy.
        contentGroup.draw(canvas, matrix, (int) newAlpha, shadowToApply);
      }
    }
  
    @Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
      contentGroup.getBounds(outBounds, parentMatrix, applyParents);
    }
  
    @Override public void onValueChanged() {
      lottieDrawable.invalidateSelf();
    }
  
    @Override public void resolveKeyPath(
        KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
      MiscUtils.resolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, this);
      for (int i = 0; i < contentGroup.getContents().size(); i++) {
        Content content = contentGroup.getContents().get(i);
        if (content instanceof KeyPathElementContent) {
          MiscUtils.resolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, (KeyPathElementContent) content);
        }
      }
    }
  
    @SuppressWarnings("unchecked")
    @Override
    public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
      if (transform.applyValueCallback(property, callback)) {
        return;
      }
  
      if (property == LottieProperty.REPEATER_COPIES) {
        copies.setValueCallback((LottieValueCallback<Float>) callback);
      } else if (property == LottieProperty.REPEATER_OFFSET) {
        offset.setValueCallback((LottieValueCallback<Float>) callback);
      }
    }
  }
  