blob: 4a721fb5eefe151e10c5a13f06daf01b891256dc [file] [log] [blame]
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.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) {
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);
contentGroup.draw(canvas, matrix, (int) newAlpha);
}
}
@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);
}
}
}