blob: ebe2ad5509d333f9d337c9fd5e371cf0abbb9708 [file] [log] [blame]
package com.airbnb.lottie.animation.content;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import com.airbnb.lottie.LottieDrawable;
import com.airbnb.lottie.animation.LPaint;
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.KeyPathElement;
import com.airbnb.lottie.model.animatable.AnimatableTransform;
import com.airbnb.lottie.model.content.ContentModel;
import com.airbnb.lottie.model.content.ShapeGroup;
import com.airbnb.lottie.model.layer.BaseLayer;
import com.airbnb.lottie.utils.Utils;
import com.airbnb.lottie.value.LottieValueCallback;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.Nullable;
public class ContentGroup implements DrawingContent, PathContent,
BaseKeyframeAnimation.AnimationListener, KeyPathElement {
private Paint offScreenPaint = new LPaint();
private RectF offScreenRectF = new RectF();
private static List<Content> contentsFromModels(LottieDrawable drawable, BaseLayer layer,
List<ContentModel> contentModels) {
List<Content> contents = new ArrayList<>(contentModels.size());
for (int i = 0; i < contentModels.size(); i++) {
Content content = contentModels.get(i).toContent(drawable, layer);
if (content != null) {
contents.add(content);
}
}
return contents;
}
@Nullable static AnimatableTransform findTransform(List<ContentModel> contentModels) {
for (int i = 0; i < contentModels.size(); i++) {
ContentModel contentModel = contentModels.get(i);
if (contentModel instanceof AnimatableTransform) {
return (AnimatableTransform) contentModel;
}
}
return null;
}
private final Matrix matrix = new Matrix();
private final Path path = new Path();
private final RectF rect = new RectF();
private final String name;
private final boolean hidden;
private final List<Content> contents;
private final LottieDrawable lottieDrawable;
@Nullable private List<PathContent> pathContents;
@Nullable private TransformKeyframeAnimation transformAnimation;
public ContentGroup(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeGroup shapeGroup) {
this(lottieDrawable, layer, shapeGroup.getName(),
shapeGroup.isHidden(), contentsFromModels(lottieDrawable, layer, shapeGroup.getItems()),
findTransform(shapeGroup.getItems()));
}
ContentGroup(final LottieDrawable lottieDrawable, BaseLayer layer,
String name, boolean hidden, List<Content> contents, @Nullable AnimatableTransform transform) {
this.name = name;
this.lottieDrawable = lottieDrawable;
this.hidden = hidden;
this.contents = contents;
if (transform != null) {
transformAnimation = transform.createAnimation();
transformAnimation.addAnimationsToLayer(layer);
transformAnimation.addListener(this);
}
List<GreedyContent> greedyContents = new ArrayList<>();
for (int i = contents.size() - 1; i >= 0; i--) {
Content content = contents.get(i);
if (content instanceof GreedyContent) {
greedyContents.add((GreedyContent) content);
}
}
for (int i = greedyContents.size() - 1; i >= 0; i--) {
greedyContents.get(i).absorbContent(contents.listIterator(contents.size()));
}
}
@Override public void onValueChanged() {
lottieDrawable.invalidateSelf();
}
@Override public String getName() {
return name;
}
@Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
// Do nothing with contents after.
List<Content> myContentsBefore = new ArrayList<>(contentsBefore.size() + contents.size());
myContentsBefore.addAll(contentsBefore);
for (int i = contents.size() - 1; i >= 0; i--) {
Content content = contents.get(i);
content.setContents(myContentsBefore, contents.subList(0, i));
myContentsBefore.add(content);
}
}
List<PathContent> getPathList() {
if (pathContents == null) {
pathContents = new ArrayList<>();
for (int i = 0; i < contents.size(); i++) {
Content content = contents.get(i);
if (content instanceof PathContent) {
pathContents.add((PathContent) content);
}
}
}
return pathContents;
}
Matrix getTransformationMatrix() {
if (transformAnimation != null) {
return transformAnimation.getMatrix();
}
matrix.reset();
return matrix;
}
@Override public Path getPath() {
// TODO: cache this somehow.
matrix.reset();
if (transformAnimation != null) {
matrix.set(transformAnimation.getMatrix());
}
path.reset();
if (hidden) {
return path;
}
for (int i = contents.size() - 1; i >= 0; i--) {
Content content = contents.get(i);
if (content instanceof PathContent) {
path.addPath(((PathContent) content).getPath(), matrix);
}
}
return path;
}
@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
if (hidden) {
return;
}
matrix.set(parentMatrix);
int layerAlpha;
if (transformAnimation != null) {
matrix.preConcat(transformAnimation.getMatrix());
int opacity = transformAnimation.getOpacity() == null ? 100 : transformAnimation.getOpacity().getValue();
layerAlpha = (int) ((opacity / 100f * parentAlpha / 255f) * 255);
} else {
layerAlpha = parentAlpha;
}
// Apply off-screen rendering only when needed in order to improve rendering performance.
boolean isRenderingWithOffScreen = lottieDrawable.isApplyingOpacityToLayersEnabled() && hasTwoOrMoreDrawableContent() && layerAlpha != 255;
if (isRenderingWithOffScreen) {
offScreenRectF.set(0, 0, 0, 0);
getBounds(offScreenRectF, matrix, true);
offScreenPaint.setAlpha(layerAlpha);
Utils.saveLayerCompat(canvas, offScreenRectF, offScreenPaint);
}
int childAlpha = isRenderingWithOffScreen ? 255 : layerAlpha;
for (int i = contents.size() - 1; i >= 0; i--) {
Object content = contents.get(i);
if (content instanceof DrawingContent) {
((DrawingContent) content).draw(canvas, matrix, childAlpha);
}
}
if (isRenderingWithOffScreen) {
canvas.restore();
}
}
private boolean hasTwoOrMoreDrawableContent() {
int drawableContentCount = 0;
for (int i = 0; i < contents.size(); i++) {
if (contents.get(i) instanceof DrawingContent) {
drawableContentCount += 1;
if (drawableContentCount >= 2) {
return true;
}
}
}
return false;
}
@Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
matrix.set(parentMatrix);
if (transformAnimation != null) {
matrix.preConcat(transformAnimation.getMatrix());
}
rect.set(0, 0, 0, 0);
for (int i = contents.size() - 1; i >= 0; i--) {
Content content = contents.get(i);
if (content instanceof DrawingContent) {
((DrawingContent) content).getBounds(rect, matrix, applyParents);
outBounds.union(rect);
}
}
}
@Override public void resolveKeyPath(
KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
if (!keyPath.matches(getName(), depth) && !"__container".equals(getName())) {
return;
}
if (!"__container".equals(getName())) {
currentPartialKeyPath = currentPartialKeyPath.addKey(getName());
if (keyPath.fullyResolvesTo(getName(), depth)) {
accumulator.add(currentPartialKeyPath.resolve(this));
}
}
if (keyPath.propagateToChildren(getName(), depth)) {
int newDepth = depth + keyPath.incrementDepthBy(getName(), depth);
for (int i = 0; i < contents.size(); i++) {
Content content = contents.get(i);
if (content instanceof KeyPathElement) {
KeyPathElement element = (KeyPathElement) content;
element.resolveKeyPath(keyPath, newDepth, accumulator, currentPartialKeyPath);
}
}
}
}
@Override
public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
if (transformAnimation != null) {
transformAnimation.applyValueCallback(property, callback);
}
}
}