• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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