• 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.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