| package com.airbnb.lottie; |
| |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.ColorFilter; |
| import android.graphics.Paint; |
| import android.graphics.PixelFormat; |
| import android.graphics.PointF; |
| import android.graphics.drawable.Drawable; |
| import android.support.annotation.ColorInt; |
| import android.support.annotation.FloatRange; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| class AnimatableLayer extends Drawable { |
| private final KeyframeAnimation.AnimationListener<Integer> integerChangedListener = |
| new KeyframeAnimation.AnimationListener<Integer>() { |
| @Override |
| public void onValueChanged(Integer progress) { |
| invalidateSelf(); |
| } |
| }; |
| private final KeyframeAnimation.AnimationListener<Float> floatChangedListener = |
| new KeyframeAnimation.AnimationListener<Float>() { |
| @Override |
| public void onValueChanged(Float progress) { |
| invalidateSelf(); |
| } |
| }; |
| private final KeyframeAnimation.AnimationListener<ScaleXY> scaleChangedListener = |
| new KeyframeAnimation.AnimationListener<ScaleXY>() { |
| @Override |
| public void onValueChanged(ScaleXY progress) { |
| invalidateSelf(); |
| } |
| }; |
| private final KeyframeAnimation.AnimationListener<PointF> pointChangedListener = |
| new KeyframeAnimation.AnimationListener<PointF>() { |
| @Override |
| public void onValueChanged(PointF progress) { |
| invalidateSelf(); |
| } |
| }; |
| |
| final List<AnimatableLayer> layers = new ArrayList<>(); |
| @Nullable private AnimatableLayer parentLayer; |
| |
| private KeyframeAnimation<PointF> position; |
| private KeyframeAnimation<PointF> anchorPoint; |
| /** |
| * This should mimic CALayer#transform |
| */ |
| private KeyframeAnimation<ScaleXY> transform; |
| private KeyframeAnimation<Integer> alpha = null; |
| private KeyframeAnimation<Float> rotation; |
| |
| private final Paint solidBackgroundPaint = new Paint(); |
| @ColorInt private int backgroundColor; |
| private final List<KeyframeAnimation<?>> animations = new ArrayList<>(); |
| @FloatRange(from = 0f, to = 1f) private float progress; |
| |
| AnimatableLayer(Drawable.Callback callback) { |
| setCallback(callback); |
| |
| solidBackgroundPaint.setAlpha(0); |
| solidBackgroundPaint.setStyle(Paint.Style.FILL); |
| } |
| |
| void setBackgroundColor(@ColorInt int color) { |
| this.backgroundColor = color; |
| solidBackgroundPaint.setColor(color); |
| invalidateSelf(); |
| } |
| |
| void addAnimation(KeyframeAnimation<?> newAnimation) { |
| animations.add(newAnimation); |
| } |
| |
| void removeAnimation(KeyframeAnimation<?> animation) { |
| animations.remove(animation); |
| } |
| |
| @Override |
| public void draw(@NonNull Canvas canvas) { |
| int saveCount = canvas.save(); |
| applyTransformForLayer(canvas, this); |
| |
| int backgroundAlpha = Color.alpha(backgroundColor); |
| if (backgroundAlpha != 0) { |
| int alpha = backgroundAlpha; |
| if (this.alpha != null) { |
| alpha = alpha * this.alpha.getValue() / 255; |
| } |
| solidBackgroundPaint.setAlpha(alpha); |
| if (alpha > 0) { |
| canvas.drawRect(getBounds(), solidBackgroundPaint); |
| } |
| } |
| for (int i = 0; i < layers.size(); i++) { |
| layers.get(i).draw(canvas); |
| } |
| canvas.restoreToCount(saveCount); |
| } |
| |
| @Override |
| public void invalidateSelf() { |
| if (parentLayer != null) { |
| parentLayer.invalidateSelf(); |
| } |
| } |
| |
| int saveCanvas(@Nullable Canvas canvas) { |
| if (canvas == null) { |
| return 0; |
| } |
| return canvas.save(); |
| } |
| |
| void restoreCanvas(@Nullable Canvas canvas, int count) { |
| if (canvas == null) { |
| return; |
| } |
| canvas.restoreToCount(count); |
| } |
| |
| void applyTransformForLayer(@Nullable Canvas canvas, AnimatableLayer layer) { |
| if (canvas == null) { |
| return; |
| } |
| // TODO: Determine if these null checks are necessary. |
| if (layer.position != null) { |
| PointF position = layer.position.getValue(); |
| if (position.x != 0 || position.y != 0) { |
| canvas.translate(position.x, position.y); |
| } |
| } |
| |
| if (layer.rotation != null) { |
| float rotation = layer.rotation.getValue(); |
| if (rotation != 0f) { |
| canvas.rotate(rotation); |
| } |
| } |
| |
| if (layer.transform != null) { |
| ScaleXY scale = layer.transform.getValue(); |
| if (scale.getScaleX() != 1f || scale.getScaleY() != 1f) { |
| canvas.scale(scale.getScaleX(), scale.getScaleY()); |
| } |
| } |
| |
| if (layer.anchorPoint != null) { |
| PointF anchorPoint = layer.anchorPoint.getValue(); |
| if (anchorPoint.x != 0 || anchorPoint.y != 0) { |
| canvas.translate(-anchorPoint.x, -anchorPoint.y); |
| } |
| } |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| throw new IllegalArgumentException("This shouldn't be used."); |
| } |
| |
| void setAlpha(KeyframeAnimation<Integer> alpha) { |
| if (this.alpha != null) { |
| removeAnimation(this.alpha); |
| this.alpha.removeUpdateListener(integerChangedListener); |
| } |
| this.alpha = alpha; |
| addAnimation(alpha); |
| alpha.addUpdateListener(integerChangedListener); |
| |
| invalidateSelf(); |
| } |
| |
| @Override |
| public int getAlpha() { |
| float alpha = this.alpha == null ? 1f : (this.alpha.getValue() / 255f); |
| float parentAlpha = parentLayer == null ? 1f : (parentLayer.getAlpha() / 255f); |
| return (int) (alpha * parentAlpha * 255); |
| } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { |
| |
| } |
| |
| void setAnchorPoint(KeyframeAnimation<PointF> anchorPoint) { |
| if (this.anchorPoint != null) { |
| removeAnimation(this.anchorPoint); |
| this.anchorPoint.removeUpdateListener(pointChangedListener); |
| } |
| this.anchorPoint = anchorPoint; |
| addAnimation(anchorPoint); |
| anchorPoint.addUpdateListener(pointChangedListener); |
| } |
| |
| void setPosition(KeyframeAnimation<PointF> position) { |
| if (this.position != null) { |
| removeAnimation(this.position); |
| this.position.removeUpdateListener(pointChangedListener); |
| } |
| this.position = position; |
| addAnimation(position); |
| position.addUpdateListener(pointChangedListener); |
| } |
| |
| void setTransform(KeyframeAnimation<ScaleXY> transform) { |
| if (this.transform != null) { |
| removeAnimation(this.transform); |
| this.transform.removeUpdateListener(scaleChangedListener); |
| } |
| this.transform = transform; |
| addAnimation(this.transform); |
| transform.addUpdateListener(scaleChangedListener); |
| } |
| |
| void setRotation(KeyframeAnimation<Float> rotation) { |
| if (this.rotation != null) { |
| removeAnimation(this.rotation); |
| this.rotation.removeUpdateListener(floatChangedListener); |
| } |
| this.rotation = rotation; |
| addAnimation(this.rotation); |
| rotation.addUpdateListener(floatChangedListener); |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.TRANSLUCENT; |
| } |
| |
| void addLayer(AnimatableLayer layer) { |
| layer.parentLayer = this; |
| layers.add(layer); |
| layer.setProgress(progress); |
| invalidateSelf(); |
| } |
| |
| void clearLayers() { |
| layers.clear(); |
| invalidateSelf(); |
| } |
| |
| public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { |
| this.progress = progress; |
| for (int i = 0; i < animations.size(); i++) { |
| animations.get(i).setProgress(progress); |
| } |
| |
| for (int i = 0; i < layers.size(); i++) { |
| layers.get(i).setProgress(progress); |
| } |
| } |
| |
| public float getProgress() { |
| return progress; |
| } |
| } |