blob: 707afdacdf4b8b0fe0579cb7ba66831428b02f5d [file] [log] [blame]
package com.airbnb.lotte;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.LongSparseArray;
import android.widget.ImageView;
import com.airbnb.lotte.layers.LotteLayer;
import com.airbnb.lotte.layers.LotteLayerView;
import com.airbnb.lotte.layers.RootLotteAnimatableLayer;
import com.airbnb.lotte.model.LotteComposition;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
public class LotteAnimationView extends ImageView {
private final LongSparseArray<LotteLayerView> layerMap = new LongSparseArray<>();
private final RootLotteAnimatableLayer rootAnimatableLayer = new RootLotteAnimatableLayer(this);
/** Can be null because it is created async */
@Nullable private LotteComposition composition;
private boolean hasInvalidatedThisFrame;
@Nullable private Bitmap mainBitmap;
@Nullable private Bitmap maskBitmap;
@Nullable private Bitmap matteBitmap;
@Nullable private Bitmap mainBitmapForMatte;
@Nullable private Bitmap maskBitmapForMatte;
@Nullable private Bitmap matteBitmapForMatte;
public LotteAnimationView(Context context) {
super(context);
init(null);
}
public LotteAnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public LotteAnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LotteAnimationView);
String fileName = ta.getString(R.styleable.LotteAnimationView_lotte_fileName);
if (fileName != null) {
setAnimation(fileName);
}
if (ta.getBoolean(R.styleable.LotteAnimationView_lotte_autoPlay, false)) {
rootAnimatableLayer.play();
}
rootAnimatableLayer.loop(ta.getBoolean(R.styleable.LotteAnimationView_lotte_loop, false));
ta.recycle();
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (rootAnimatableLayer != null && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
setMeasuredDimension(rootAnimatableLayer.getBounds().width(), rootAnimatableLayer.getBounds().height());
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable drawable) {
return true;
}
@Override
public void invalidateDrawable(@NonNull Drawable dr) {
if (!hasInvalidatedThisFrame) {
super.invalidateDrawable(rootAnimatableLayer);
hasInvalidatedThisFrame = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
hasInvalidatedThisFrame = false;
super.onDraw(canvas);
}
@Override
protected void onDetachedFromWindow() {
if (mainBitmap != null) {
mainBitmap.recycle();
}
if (maskBitmap != null) {
maskBitmap.recycle();
}
if (matteBitmap != null) {
matteBitmap.recycle();
}
if (mainBitmapForMatte != null) {
mainBitmapForMatte.recycle();
}
if (maskBitmapForMatte != null) {
maskBitmapForMatte.recycle();
}
if (matteBitmapForMatte != null) {
matteBitmapForMatte.recycle();
}
super.onDetachedFromWindow();
}
public void setAnimation(String animationName) {
InputStream file;
try {
file = getContext().getAssets().open(animationName);
} catch (IOException e) {
throw new IllegalStateException("Unable to find file " + animationName, e);
}
new AsyncTask<InputStream, Void, JSONObject>() {
@Override
protected JSONObject doInBackground(InputStream... params) {
//noinspection WrongThread
return setAnimationSync(params[0]);
}
@Override
protected void onPostExecute(JSONObject jsonObject) {
setJson(jsonObject);
}
}.execute(file);
}
public void setAnimationSync(String animationName) {
InputStream file;
try {
file = getContext().getAssets().open(animationName);
} catch (IOException e) {
throw new IllegalStateException("Unable to find file " + animationName, e);
}
setJsonSync(setAnimationSync(file));
}
private JSONObject setAnimationSync(InputStream file) {
try {
int size = file.available();
byte[] buffer = new byte[size];
//noinspection ResultOfMethodCallIgnored
file.read(buffer);
file.close();
String json = new String(buffer, "UTF-8");
return new JSONObject(json);
} catch (IOException e) {
throw new IllegalStateException("Unable to find file.", e);
} catch (JSONException e) {
throw new IllegalStateException("Unable to load JSON.", e);
}
}
public void setJson(JSONObject json) {
// TODO: cancel these if the iew gets detached.
new AsyncTask<JSONObject, Void, LotteComposition>() {
@Override
protected LotteComposition doInBackground(JSONObject... params) {
return LotteComposition.fromJson(params[0]);
}
@Override
protected void onPostExecute(LotteComposition model) {
setComposition(model);
}
}.execute(json);
}
private void setJsonSync(JSONObject json) {
LotteComposition composition = LotteComposition.fromJson(json);
setComposition(composition);
}
public void setComposition(@NonNull LotteComposition composition) {
this.composition = composition;
rootAnimatableLayer.setCompDuration(composition.getDuration());
rootAnimatableLayer.setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height());
buildSubviewsForComposition();
requestLayout();
setImageDrawable(rootAnimatableLayer);
}
private void buildSubviewsForComposition() {
//noinspection ConstantConditions
List<LotteLayer> reversedLayers = composition.getLayers();
Collections.reverse(reversedLayers);
Rect bounds = composition.getBounds();
mainBitmap = (composition.hasMasks() || composition.hasMattes()) ? Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888) : null;
maskBitmap = composition.hasMasks() ? Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8) : null;
matteBitmap = composition.hasMattes() ? Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888) : null;
mainBitmapForMatte = null;
maskBitmapForMatte = null;
matteBitmapForMatte = null;
LotteLayerView maskedLayer = null;
for (int i = 0; i < reversedLayers.size(); i++) {
LotteLayer layer = reversedLayers.get(i);
LotteLayerView layerView;
if (maskedLayer == null) {
layerView = new LotteLayerView(layer, composition, this, mainBitmap, maskBitmap, matteBitmap);
} else {
if (mainBitmapForMatte == null) {
mainBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
// TODO: only create matte mask and matte if necessary.
maskBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
matteBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
}
layerView = new LotteLayerView(layer, composition, this, mainBitmapForMatte, maskBitmapForMatte, matteBitmapForMatte);
}
layerMap.put(layerView.getId(), layerView);
if (maskedLayer != null) {
maskedLayer.setMatte(layerView);
maskedLayer = null;
} else {
if (layer.getMatteType() == LotteLayer.MatteType.Add) {
maskedLayer = layerView;
}
rootAnimatableLayer.addLayer(layerView);
}
}
}
public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) {
rootAnimatableLayer.addAnimatorUpdateListener(updateListener);
}
public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) {
rootAnimatableLayer.removeAnimatorUpdateListener(updateListener);
}
public void addAnimatorListener(Animator.AnimatorListener listener) {
rootAnimatableLayer.addAnimatorListener(listener);
}
public void removeAnimatorListener(Animator.AnimatorListener listener) {
rootAnimatableLayer.removeAnimatorListener(listener);
}
public void loop(boolean loop) {
rootAnimatableLayer.loop(loop);
}
public boolean isAnimating() {
return rootAnimatableLayer.isAnimating();
}
public void playAnimation() {
rootAnimatableLayer.play();
}
public void cancelAnimation() {
rootAnimatableLayer.cancelAnimation();
}
public void setProgress(@FloatRange(from=0f, to=1f) float progress) {
rootAnimatableLayer.setProgress(progress);
}
public int getFrameRate() {
return composition != null ? composition.getFrameRate() : 60;
}
public long getDuration() {
return composition != null ? composition.getDuration() : 0;
}
}