1 package com.airbnb.lottie; 2 3 import android.content.Context; 4 import android.content.res.Resources; 5 import android.graphics.Rect; 6 7 import androidx.annotation.NonNull; 8 import androidx.annotation.Nullable; 9 import androidx.annotation.RawRes; 10 import androidx.annotation.RestrictTo; 11 import androidx.annotation.WorkerThread; 12 import androidx.collection.LongSparseArray; 13 import androidx.collection.SparseArrayCompat; 14 15 import com.airbnb.lottie.model.Font; 16 import com.airbnb.lottie.model.FontCharacter; 17 import com.airbnb.lottie.model.Marker; 18 import com.airbnb.lottie.model.layer.Layer; 19 import com.airbnb.lottie.parser.moshi.JsonReader; 20 import com.airbnb.lottie.utils.Logger; 21 import com.airbnb.lottie.utils.MiscUtils; 22 23 import org.json.JSONObject; 24 25 import java.io.InputStream; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.HashSet; 29 import java.util.List; 30 import java.util.Map; 31 32 /** 33 * After Effects/Bodymovin composition model. This is the serialized model from which the 34 * animation will be created. It is designed to be stateless, cacheable, and shareable. 35 * <p> 36 * To create one, use {@link LottieCompositionFactory}. 37 * <p> 38 * It can be used with a {@link com.airbnb.lottie.LottieAnimationView} or 39 * {@link com.airbnb.lottie.LottieDrawable}. 40 */ 41 public class LottieComposition { 42 43 private final PerformanceTracker performanceTracker = new PerformanceTracker(); 44 private final HashSet<String> warnings = new HashSet<>(); 45 private Map<String, List<Layer>> precomps; 46 private Map<String, LottieImageAsset> images; 47 /** 48 * Map of font names to fonts 49 */ 50 private Map<String, Font> fonts; 51 private List<Marker> markers; 52 private SparseArrayCompat<FontCharacter> characters; 53 private LongSparseArray<Layer> layerMap; 54 private List<Layer> layers; 55 // This is stored as a set to avoid duplicates. 56 private Rect bounds; 57 private float startFrame; 58 private float endFrame; 59 private float frameRate; 60 /** 61 * Used to determine if an animation can be drawn with hardware acceleration. 62 */ 63 private boolean hasDashPattern; 64 /** 65 * Counts the number of mattes and masks. Before Android switched to SKIA 66 * for drawing in Oreo (API 28), using hardware acceleration with mattes and masks 67 * was only faster until you had ~4 masks after which it would actually become slower. 68 */ 69 private int maskAndMatteCount = 0; 70 71 @RestrictTo(RestrictTo.Scope.LIBRARY) init(Rect bounds, float startFrame, float endFrame, float frameRate, List<Layer> layers, LongSparseArray<Layer> layerMap, Map<String, List<Layer>> precomps, Map<String, LottieImageAsset> images, SparseArrayCompat<FontCharacter> characters, Map<String, Font> fonts, List<Marker> markers)72 public void init(Rect bounds, float startFrame, float endFrame, float frameRate, 73 List<Layer> layers, LongSparseArray<Layer> layerMap, Map<String, 74 List<Layer>> precomps, Map<String, LottieImageAsset> images, 75 SparseArrayCompat<FontCharacter> characters, Map<String, Font> fonts, 76 List<Marker> markers) { 77 this.bounds = bounds; 78 this.startFrame = startFrame; 79 this.endFrame = endFrame; 80 this.frameRate = frameRate; 81 this.layers = layers; 82 this.layerMap = layerMap; 83 this.precomps = precomps; 84 this.images = images; 85 this.characters = characters; 86 this.fonts = fonts; 87 this.markers = markers; 88 } 89 90 @RestrictTo(RestrictTo.Scope.LIBRARY) addWarning(String warning)91 public void addWarning(String warning) { 92 Logger.warning(warning); 93 warnings.add(warning); 94 } 95 96 @RestrictTo(RestrictTo.Scope.LIBRARY) setHasDashPattern(boolean hasDashPattern)97 public void setHasDashPattern(boolean hasDashPattern) { 98 this.hasDashPattern = hasDashPattern; 99 } 100 101 @RestrictTo(RestrictTo.Scope.LIBRARY) incrementMatteOrMaskCount(int amount)102 public void incrementMatteOrMaskCount(int amount) { 103 maskAndMatteCount += amount; 104 } 105 106 /** 107 * Used to determine if an animation can be drawn with hardware acceleration. 108 */ 109 @RestrictTo(RestrictTo.Scope.LIBRARY) hasDashPattern()110 public boolean hasDashPattern() { 111 return hasDashPattern; 112 } 113 114 /** 115 * Used to determine if an animation can be drawn with hardware acceleration. 116 */ 117 @RestrictTo(RestrictTo.Scope.LIBRARY) getMaskAndMatteCount()118 public int getMaskAndMatteCount() { 119 return maskAndMatteCount; 120 } 121 getWarnings()122 public ArrayList<String> getWarnings() { 123 return new ArrayList<>(Arrays.asList(warnings.toArray(new String[warnings.size()]))); 124 } 125 setPerformanceTrackingEnabled(boolean enabled)126 @SuppressWarnings("WeakerAccess") public void setPerformanceTrackingEnabled(boolean enabled) { 127 performanceTracker.setEnabled(enabled); 128 } 129 getPerformanceTracker()130 public PerformanceTracker getPerformanceTracker() { 131 return performanceTracker; 132 } 133 134 @RestrictTo(RestrictTo.Scope.LIBRARY) layerModelForId(long id)135 public Layer layerModelForId(long id) { 136 return layerMap.get(id); 137 } 138 getBounds()139 @SuppressWarnings("WeakerAccess") public Rect getBounds() { 140 return bounds; 141 } 142 getDuration()143 @SuppressWarnings("WeakerAccess") public float getDuration() { 144 return (long) (getDurationFrames() / frameRate * 1000); 145 } 146 getStartFrame()147 public float getStartFrame() { 148 return startFrame; 149 } 150 getEndFrame()151 public float getEndFrame() { 152 return endFrame; 153 } 154 getFrameForProgress(float progress)155 public float getFrameForProgress(float progress) { 156 return MiscUtils.lerp(startFrame, endFrame, progress); 157 } 158 getProgressForFrame(float frame)159 public float getProgressForFrame(float frame) { 160 float framesSinceStart = frame - startFrame; 161 float frameRange = endFrame - startFrame; 162 return framesSinceStart / frameRange; 163 } 164 getFrameRate()165 public float getFrameRate() { 166 return frameRate; 167 } 168 getLayers()169 public List<Layer> getLayers() { 170 return layers; 171 } 172 173 @RestrictTo(RestrictTo.Scope.LIBRARY) 174 @Nullable getPrecomps(String id)175 public List<Layer> getPrecomps(String id) { 176 return precomps.get(id); 177 } 178 getCharacters()179 public SparseArrayCompat<FontCharacter> getCharacters() { 180 return characters; 181 } 182 getFonts()183 public Map<String, Font> getFonts() { 184 return fonts; 185 } 186 getMarkers()187 public List<Marker> getMarkers() { 188 return markers; 189 } 190 191 @Nullable getMarker(String markerName)192 public Marker getMarker(String markerName) { 193 int size = markers.size(); 194 for (int i = 0; i < size; i++) { 195 Marker marker = markers.get(i); 196 if (marker.matchesName(markerName)) { 197 return marker; 198 } 199 } 200 return null; 201 } 202 hasImages()203 public boolean hasImages() { 204 return !images.isEmpty(); 205 } 206 207 /** 208 * Returns a map of image asset id to {@link LottieImageAsset}. These assets contain image metadata exported 209 * from After Effects or other design tool. The resulting Bitmaps can be set directly on the image asset so 210 * they can be loaded once and reused across compositions. 211 */ getImages()212 public Map<String, LottieImageAsset> getImages() { 213 return images; 214 } 215 getDurationFrames()216 public float getDurationFrames() { 217 return endFrame - startFrame; 218 } 219 220 221 @NonNull 222 @Override toString()223 public String toString() { 224 final StringBuilder sb = new StringBuilder("LottieComposition:\n"); 225 for (Layer layer : layers) { 226 sb.append(layer.toString("\t")); 227 } 228 return sb.toString(); 229 } 230 231 /** 232 * This will be removed in the next version of Lottie. {@link LottieCompositionFactory} has improved 233 * API names, failure handlers, and will return in-progress tasks so you will never parse the same 234 * animation twice in parallel. 235 * 236 * @see LottieCompositionFactory 237 */ 238 @Deprecated 239 public static class Factory { Factory()240 private Factory() { 241 } 242 243 /** 244 * @see LottieCompositionFactory#fromAsset(Context, String) 245 */ 246 @SuppressWarnings("deprecation") 247 @Deprecated fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener l)248 public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener l) { 249 ListenerAdapter listener = new ListenerAdapter(l); 250 LottieCompositionFactory.fromAsset(context, fileName).addListener(listener); 251 return listener; 252 } 253 254 /** 255 * @see LottieCompositionFactory#fromRawRes(Context, int) 256 */ 257 @SuppressWarnings("deprecation") 258 @Deprecated fromRawFile(Context context, @RawRes int resId, OnCompositionLoadedListener l)259 public static Cancellable fromRawFile(Context context, @RawRes int resId, OnCompositionLoadedListener l) { 260 ListenerAdapter listener = new ListenerAdapter(l); 261 LottieCompositionFactory.fromRawRes(context, resId).addListener(listener); 262 return listener; 263 } 264 265 /** 266 * @see LottieCompositionFactory#fromJsonInputStream(InputStream, String) 267 */ 268 @SuppressWarnings("deprecation") 269 @Deprecated fromInputStream(InputStream stream, OnCompositionLoadedListener l)270 public static Cancellable fromInputStream(InputStream stream, OnCompositionLoadedListener l) { 271 ListenerAdapter listener = new ListenerAdapter(l); 272 LottieCompositionFactory.fromJsonInputStream(stream, null).addListener(listener); 273 return listener; 274 } 275 276 /** 277 * @see LottieCompositionFactory#fromJsonString(String, String) 278 */ 279 @SuppressWarnings("deprecation") 280 @Deprecated fromJsonString(String jsonString, OnCompositionLoadedListener l)281 public static Cancellable fromJsonString(String jsonString, OnCompositionLoadedListener l) { 282 ListenerAdapter listener = new ListenerAdapter(l); 283 LottieCompositionFactory.fromJsonString(jsonString, null).addListener(listener); 284 return listener; 285 } 286 287 /** 288 * @see LottieCompositionFactory#fromJsonReader(JsonReader, String) 289 */ 290 @SuppressWarnings("deprecation") 291 @Deprecated fromJsonReader(JsonReader reader, OnCompositionLoadedListener l)292 public static Cancellable fromJsonReader(JsonReader reader, OnCompositionLoadedListener l) { 293 ListenerAdapter listener = new ListenerAdapter(l); 294 LottieCompositionFactory.fromJsonReader(reader, null).addListener(listener); 295 return listener; 296 } 297 298 /** 299 * @see LottieCompositionFactory#fromAssetSync(Context, String) 300 */ 301 @Nullable 302 @WorkerThread 303 @Deprecated fromFileSync(Context context, String fileName)304 public static LottieComposition fromFileSync(Context context, String fileName) { 305 return LottieCompositionFactory.fromAssetSync(context, fileName).getValue(); 306 } 307 308 /** 309 * @see LottieCompositionFactory#fromJsonInputStreamSync(InputStream, String) 310 */ 311 @Nullable 312 @WorkerThread 313 @Deprecated fromInputStreamSync(InputStream stream)314 public static LottieComposition fromInputStreamSync(InputStream stream) { 315 return LottieCompositionFactory.fromJsonInputStreamSync(stream, null).getValue(); 316 } 317 318 /** 319 * This will now auto-close the input stream! 320 * 321 * @see LottieCompositionFactory#fromJsonInputStreamSync(InputStream, String) 322 */ 323 @Nullable 324 @WorkerThread 325 @Deprecated fromInputStreamSync(InputStream stream, boolean close)326 public static LottieComposition fromInputStreamSync(InputStream stream, boolean close) { 327 if (close) { 328 Logger.warning("Lottie now auto-closes input stream!"); 329 } 330 return LottieCompositionFactory.fromJsonInputStreamSync(stream, null).getValue(); 331 } 332 333 /** 334 * @see LottieCompositionFactory#fromJsonSync(JSONObject, String) 335 */ 336 @Nullable 337 @WorkerThread 338 @Deprecated fromJsonSync(@uppressWarnings"unused") Resources res, JSONObject json)339 public static LottieComposition fromJsonSync(@SuppressWarnings("unused") Resources res, JSONObject json) { 340 //noinspection deprecation 341 return LottieCompositionFactory.fromJsonSync(json, null).getValue(); 342 } 343 344 /** 345 * @see LottieCompositionFactory#fromJsonStringSync(String, String) 346 */ 347 @Nullable 348 @WorkerThread 349 @Deprecated fromJsonSync(String json)350 public static LottieComposition fromJsonSync(String json) { 351 return LottieCompositionFactory.fromJsonStringSync(json, null).getValue(); 352 } 353 354 /** 355 * @see LottieCompositionFactory#fromJsonReaderSync(JsonReader, String) 356 */ 357 @Nullable 358 @WorkerThread 359 @Deprecated fromJsonSync(JsonReader reader)360 public static LottieComposition fromJsonSync(JsonReader reader) { 361 return LottieCompositionFactory.fromJsonReaderSync(reader, null).getValue(); 362 } 363 364 @SuppressWarnings("deprecation") 365 private static final class ListenerAdapter implements LottieListener<LottieComposition>, Cancellable { 366 private final OnCompositionLoadedListener listener; 367 private boolean cancelled = false; 368 ListenerAdapter(OnCompositionLoadedListener listener)369 private ListenerAdapter(OnCompositionLoadedListener listener) { 370 this.listener = listener; 371 } 372 onResult(LottieComposition composition)373 @Override public void onResult(LottieComposition composition) { 374 if (cancelled) { 375 return; 376 } 377 listener.onCompositionLoaded(composition); 378 } 379 cancel()380 @Override public void cancel() { 381 cancelled = true; 382 } 383 } 384 } 385 }