package com.airbnb.lottie; import android.graphics.Color; import android.support.annotation.Nullable; import android.util.Log; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; class Layer { private static final String TAG = Layer.class.getSimpleName(); enum LayerType { PreComp, Solid, Image, Null, Shape, Text, Unknown } enum MatteType { None, Add, Invert, Unknown } private final List<Object> shapes; private final LottieComposition composition; private final String layerName; private final long layerId; private final LayerType layerType; private final long parentId; @Nullable private final String refId; private final List<Mask> masks; private final AnimatableTransform transform; private final int solidWidth; private final int solidHeight; private final int solidColor; private final float timeStretch; private final float startProgress; private final int preCompWidth; private final int preCompHeight; private final List<Keyframe<Float>> inOutKeyframes; private final MatteType matteType; private Layer(List<Object> shapes, LottieComposition composition, String layerName, long layerId, LayerType layerType, long parentId, @Nullable String refId, List<Mask> masks, AnimatableTransform transform, int solidWidth, int solidHeight, int solidColor, float timeStretch, float startProgress, int preCompWidth, int preCompHeight, List<Keyframe<Float>> inOutKeyframes, MatteType matteType) { this.shapes = shapes; this.composition = composition; this.layerName = layerName; this.layerId = layerId; this.layerType = layerType; this.parentId = parentId; this.refId = refId; this.masks = masks; this.transform = transform; this.solidWidth = solidWidth; this.solidHeight = solidHeight; this.solidColor = solidColor; this.timeStretch = timeStretch; this.startProgress = startProgress; this.preCompWidth = preCompWidth; this.preCompHeight = preCompHeight; this.inOutKeyframes = inOutKeyframes; this.matteType = matteType; } LottieComposition getComposition() { return composition; } float getTimeStretch() { return timeStretch; } float getStartProgress() { return startProgress; } List<Keyframe<Float>> getInOutKeyframes() { return inOutKeyframes; } long getId() { return layerId; } String getName() { return layerName; } @Nullable String getRefId() { return refId; } int getPreCompWidth() { return preCompWidth; } int getPreCompHeight() { return preCompHeight; } List<Mask> getMasks() { return masks; } LayerType getLayerType() { return layerType; } MatteType getMatteType() { return matteType; } long getParentId() { return parentId; } List<Object> getShapes() { return shapes; } AnimatableTransform getTransform() { return transform; } int getSolidColor() { return solidColor; } int getSolidHeight() { return solidHeight; } int getSolidWidth() { return solidWidth; } @Override public String toString() { return toString(""); } String toString(String prefix) { StringBuilder sb = new StringBuilder(); sb.append(prefix).append(getName()).append("\n"); Layer parent = composition.layerModelForId(getParentId()); if (parent != null) { sb.append("\t\tParents: ").append(parent.getName()); parent = composition.layerModelForId(parent.getParentId()); while (parent != null) { sb.append("->").append(parent.getName()); parent = composition.layerModelForId(parent.getParentId()); } sb.append(prefix).append("\n"); } if (!getMasks().isEmpty()) { sb.append(prefix).append("\tMasks: ").append(getMasks().size()).append("\n"); } if (getSolidWidth() != 0 && getSolidHeight() != 0) { sb.append(prefix).append("\tBackground: ").append(String .format(Locale.US, "%dx%d %X\n", getSolidWidth(), getSolidHeight(), getSolidColor())); } if (!shapes.isEmpty()) { sb.append(prefix).append("\tShapes:\n"); for (Object shape : shapes) { sb.append(prefix).append("\t\t").append(shape).append("\n"); } } return sb.toString(); } static class Factory { private Factory() { } static Layer newInstance(LottieComposition composition) { // TODO: make sure in out keyframes work return new Layer(Collections.emptyList(), composition, null, -1, LayerType.PreComp, -1, null, Collections.<Mask>emptyList(), AnimatableTransform.Factory.newInstance(), 0, 0, 0, 0, 0, 0, 0, Collections.<Keyframe<Float>>emptyList(), MatteType.None); } static Layer newInstance(JSONObject json, LottieComposition composition) { String layerName = json.optString("nm"); String refId = json.optString("refId"); long layerId = json.optLong("ind"); int solidWidth = 0; int solidHeight = 0; int solidColor = 0; int preCompWidth = 0; int preCompHeight = 0; LayerType layerType; int layerTypeInt = json.optInt("ty", -1); if (layerTypeInt < LayerType.Unknown.ordinal()) { layerType = LayerType.values()[layerTypeInt]; } else { layerType = LayerType.Unknown; } long parentId = json.optLong("parent", -1); if (layerType == LayerType.Solid) { solidWidth = (int) (json.optInt("sw") * composition.getDpScale()); solidHeight = (int) (json.optInt("sh") * composition.getDpScale()); solidColor = Color.parseColor(json.optString("sc")); if (L.DBG) { Log.d(TAG, "\tSolid=" + Integer.toHexString(solidColor) + " " + solidWidth + "x" + solidHeight + " " + composition.getBounds()); } } AnimatableTransform transform = AnimatableTransform.Factory.newInstance(json.optJSONObject("ks"), composition); MatteType matteType = MatteType.values()[json.optInt("tt")]; List<Object> shapes = new ArrayList<>(); List<Mask> masks = new ArrayList<>(); List<Keyframe<Float>> inOutKeyframes = new ArrayList<>(); JSONArray jsonMasks = json.optJSONArray("masksProperties"); if (jsonMasks != null) { for (int i = 0; i < jsonMasks.length(); i++) { Mask mask = Mask.Factory.newMask(jsonMasks.optJSONObject(i), composition); masks.add(mask); } } JSONArray shapesJson = json.optJSONArray("shapes"); if (shapesJson != null) { for (int i = 0; i < shapesJson.length(); i++) { Object shape = ShapeGroup.shapeItemWithJson(shapesJson.optJSONObject(i), composition); if (shape != null) { shapes.add(shape); } } } float timeStretch = (float) json.optDouble("sr", 1.0); float startFrame = (float) json.optDouble("st"); float frames = composition.getDurationFrames(); float startProgress = startFrame / frames; if (layerType == LayerType.PreComp) { preCompWidth = (int) (json.optInt("w") * composition.getDpScale()); preCompHeight = (int) (json.optInt("h") * composition.getDpScale()); } float inFrame = json.optLong("ip"); float outFrame = json.optLong("op"); // Before the in frame if (inFrame > 0) { Keyframe<Float> preKeyframe = new Keyframe<>(composition, 0f, 0f, null, 0f, inFrame); inOutKeyframes.add(preKeyframe); } // The + 1 is because the animation should be visible on the out frame itself. outFrame = (outFrame > 0 ? outFrame : composition.getEndFrame() + 1); Keyframe<Float> visibleKeyframe = new Keyframe<>(composition, 1f, 1f, null, inFrame, outFrame); inOutKeyframes.add(visibleKeyframe); if (outFrame <= composition.getDurationFrames()) { Keyframe<Float> outKeyframe = new Keyframe<>(composition, 0f, 0f, null, outFrame, (float) composition.getEndFrame()); inOutKeyframes.add(outKeyframe); } return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId, masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startProgress, preCompWidth, preCompHeight, inOutKeyframes, matteType); } } }