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