blob: 7f5d49df981ad8a82b57c30f77c924686c7a0cab [file] [log] [blame]
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;
}
}