blob: 54eee106a71d9f0b0cd8d1e4cf1427bad6fcaa1f [file] [log] [blame]
package com.airbnb.lottie;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
class StrokeContent implements DrawingContent, BaseKeyframeAnimation.AnimationListener {
private final Path path = new Path();
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final LottieDrawable lottieDrawable;
private final List<PathContent> paths = new ArrayList<>();
private final float[] dashPatternValues;
private final BaseKeyframeAnimation<?, Integer> colorAnimation;
private final BaseKeyframeAnimation<?, Float> widthAnimation;
private final BaseKeyframeAnimation<?, Integer> opacityAnimation;
private final List<BaseKeyframeAnimation<?, Float>> dashPatternAnimations;
@Nullable private final BaseKeyframeAnimation<?, Float> dashPatternOffsetAnimation;
StrokeContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeStroke stroke) {
this.lottieDrawable = lottieDrawable;
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(stroke.getCapType().toPaintCap());
paint.setStrokeJoin(stroke.getJoinType().toPaintJoin());
colorAnimation = stroke.getColor().createAnimation();
opacityAnimation = stroke.getOpacity().createAnimation();
widthAnimation = stroke.getWidth().createAnimation();
if (stroke.getDashOffset() == null) {
dashPatternOffsetAnimation = null;
} else {
dashPatternOffsetAnimation = stroke.getDashOffset().createAnimation();
}
List<AnimatableFloatValue> dashPattern = stroke.getLineDashPattern();
dashPatternAnimations = new ArrayList<>(dashPattern.size());
dashPatternValues = new float[dashPattern.size()];
for (int i = 0; i < dashPattern.size(); i++) {
dashPatternAnimations.add(dashPattern.get(i).createAnimation());
}
layer.addAnimation(colorAnimation);
layer.addAnimation(opacityAnimation);
layer.addAnimation(widthAnimation);
for (int i = 0; i < dashPatternAnimations.size(); i++) {
layer.addAnimation(dashPatternAnimations.get(i));
}
if (dashPatternOffsetAnimation != null) {
layer.addAnimation(dashPatternOffsetAnimation);
}
colorAnimation.addUpdateListener(this);
opacityAnimation.addUpdateListener(this);
widthAnimation.addUpdateListener(this);
for (int i = 0; i < dashPattern.size(); i++) {
dashPatternAnimations.get(i).addUpdateListener(this);
}
if (dashPatternOffsetAnimation != null) {
dashPatternOffsetAnimation.addUpdateListener(this);
}
}
@Override public void onValueChanged() {
lottieDrawable.invalidateSelf();
}
@Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
for (int i = 0; i < contentsAfter.size(); i++) {
Content content = contentsAfter.get(i);
if (content instanceof PathContent) {
paths.add((PathContent) content);
}
}
}
@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
paint.setColor(colorAnimation.getValue());
int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
paint.setAlpha(alpha);
paint.setStrokeWidth(widthAnimation.getValue() * Utils.getScale(parentMatrix));
if (paint.getStrokeWidth() < 1) {
// Android draws a hairline stroke for 0, After Effects doesn't.
return;
}
applyDashPatternIfNeeded();
path.reset();
for (int i = 0; i < paths.size(); i++) {
this.path.addPath(paths.get(i).getPath(), parentMatrix);
}
canvas.drawPath(path, paint);
}
private void applyDashPatternIfNeeded() {
if (dashPatternAnimations.isEmpty()) {
return;
}
float scale = lottieDrawable.getScale();
for (int i = 0; i < dashPatternAnimations.size(); i++) {
dashPatternValues[i] = dashPatternAnimations.get(i).getValue();
// If the value of the dash pattern or gap is too small, the number of individual sections
// approaches infinity as the value approaches 0.
// To mitigate this, we essentially put a minimum value on the dash pattern size of 1px
// and a minimum gap size of 0.01.
if (i % 2 == 0) {
if (dashPatternValues[i] < 1f) {
dashPatternValues[i] = 1f;
}
} else {
if (dashPatternValues[i] < 0.1f) {
dashPatternValues[i] = 0.1f;
}
}
dashPatternValues[i] *= scale;
}
float offset = dashPatternOffsetAnimation == null ? 0f : dashPatternOffsetAnimation.getValue();
paint.setPathEffect(new DashPathEffect(dashPatternValues, offset));
}
}