package com.airbnb.lottie; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.support.annotation.FloatRange; import android.support.annotation.Nullable; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.List; abstract class BaseLayer implements DrawingContent, BaseKeyframeAnimation.AnimationListener { private static final int SAVE_FLAGS = Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG; static BaseLayer forModel( Layer layerModel, LottieDrawable drawable, LottieComposition composition) { switch (layerModel.getLayerType()) { case Shape: return new ShapeLayer(drawable, layerModel); case PreComp: return new CompositionLayer(drawable, layerModel, composition.getPrecomps(layerModel.getRefId()), composition); case Solid: return new SolidLayer(drawable, layerModel); case Image: return new ImageLayer(drawable, layerModel); case Null: return new NullLayer(drawable, layerModel); case Text: case Unknown: default: // Do nothing Log.w(L.TAG, "Unknown layer type " + layerModel.getLayerType()); return new NullLayer(drawable, layerModel); } } private final Path path = new Path(); private final Matrix matrix = new Matrix(); private final Paint contentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mattePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint clearPaint = new Paint(); private final RectF rect = new RectF(); final LottieDrawable lottieDrawable; final Layer layerModel; @Nullable private MaskKeyframeAnimation mask; @Nullable private BaseLayer matteLayer; @Nullable private BaseLayer parentLayer; private List<BaseLayer> parentLayers; private final List<BaseKeyframeAnimation<?, ?>> animations = new ArrayList<>(); final TransformKeyframeAnimation transform; private boolean visible = true; BaseLayer(LottieDrawable lottieDrawable, Layer layerModel) { this.lottieDrawable = lottieDrawable; this.layerModel = layerModel; clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); if (layerModel.getMatteType() == Layer.MatteType.Invert) { mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); } else { mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); } this.transform = layerModel.getTransform().createAnimation(); transform.addListener(this); transform.addAnimationsToLayer(this); if (layerModel.getMasks() != null && !layerModel.getMasks().isEmpty()) { this.mask = new MaskKeyframeAnimation(layerModel.getMasks()); for (BaseKeyframeAnimation<?, Path> animation : mask.getMaskAnimations()) { addAnimation(animation); animation.addUpdateListener(this); } } setupInOutAnimations(); } @Override public void onValueChanged() { invalidateSelf(); } Layer getLayerModel() { return layerModel; } void setMatteLayer(@Nullable BaseLayer matteLayer) { this.matteLayer = matteLayer; } boolean hasMatteOnThisLayer() { return matteLayer != null; } void setParentLayer(@Nullable BaseLayer parentLayer) { this.parentLayer = parentLayer; } private void setupInOutAnimations() { if (!layerModel.getInOutKeyframes().isEmpty()) { final FloatKeyframeAnimation inOutAnimation = new FloatKeyframeAnimation(layerModel.getInOutKeyframes()); inOutAnimation.setIsDiscrete(); inOutAnimation.addUpdateListener(new BaseKeyframeAnimation.AnimationListener() { @Override public void onValueChanged() { setVisible(inOutAnimation.getValue() == 1f); } }); setVisible(inOutAnimation.getValue() == 1f); addAnimation(inOutAnimation); } else { setVisible(true); } } private void invalidateSelf() { lottieDrawable.invalidateSelf(); } void addAnimation(BaseKeyframeAnimation<?, ?> newAnimation) { if (!(newAnimation instanceof StaticKeyframeAnimation)) { animations.add(newAnimation); } } @Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) { if (!visible) { return; } buildParentLayerListIfNeeded(); matrix.reset(); matrix.set(parentMatrix); for (int i = parentLayers.size() - 1; i >= 0; i--) { matrix.preConcat(parentLayers.get(i).transform.getMatrix()); } matrix.preConcat(transform.getMatrix()); int alpha = (int) ((parentAlpha / 255f * (float) transform.getOpacity().getValue() / 100f) * 255); if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) { drawLayer(canvas, matrix, alpha); return; } // TODO: make sure this is the right clip. rect.set(canvas.getClipBounds()); canvas.saveLayer(rect, contentPaint, Canvas.ALL_SAVE_FLAG); // Clear the off screen buffer. This is necessary for some phones. canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), clearPaint); drawLayer(canvas, matrix, alpha); if (hasMasksOnThisLayer()) { applyMasks(canvas, matrix); } if (hasMatteOnThisLayer()) { canvas.saveLayer(rect, mattePaint, SAVE_FLAGS); canvas.drawRect(rect, clearPaint); //noinspection ConstantConditions matteLayer.draw(canvas, parentMatrix, alpha); canvas.restore(); } canvas.restore(); } abstract void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha); private void applyMasks(Canvas canvas, Matrix matrix) { canvas.saveLayer(rect, maskPaint, SAVE_FLAGS); canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), clearPaint); //noinspection ConstantConditions int size = mask.getMasks().size(); for (int i = 0; i < size; i++) { Mask mask = this.mask.getMasks().get(i); BaseKeyframeAnimation<?, Path> maskAnimation = this.mask.getMaskAnimations().get(i); Path maskPath = maskAnimation.getValue(); path.set(maskPath); path.transform(matrix); switch (mask.getMaskMode()) { case MaskModeSubtract: maskPath.setFillType(Path.FillType.INVERSE_WINDING); break; case MaskModeAdd: default: maskPath.setFillType(Path.FillType.WINDING); } canvas.drawPath(path, contentPaint); } canvas.restore(); } boolean hasMasksOnThisLayer() { return mask != null && !mask.getMaskAnimations().isEmpty(); } private void setVisible(boolean visible) { if (visible != this.visible) { this.visible = visible; invalidateSelf(); } } void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { if (matteLayer != null) { matteLayer.setProgress(progress); } for (int i = 0; i < animations.size(); i++) { animations.get(i).setProgress(progress); } } private void buildParentLayerListIfNeeded() { if (parentLayers != null) { return; } if (parentLayer == null) { parentLayers = Collections.emptyList(); return; } parentLayers = new ArrayList<>(); BaseLayer layer = parentLayer; while (layer != null) { parentLayers.add(layer); layer = layer.parentLayer; } } @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) { // Do nothing } }