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