| 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)); |
| } |
| } |