xref: /aosp_15_r20/external/lottie/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt (revision bb5273fecd5c61b9ace70f9ff4fcd88f0e12e3f7)

<lambda>null1 package com.airbnb.lottie.compose
2 
3 import android.graphics.Matrix
4 import android.graphics.Typeface
5 import androidx.annotation.FloatRange
6 import androidx.compose.foundation.Canvas
7 import androidx.compose.foundation.layout.Box
8 import androidx.compose.runtime.Composable
9 import androidx.compose.runtime.getValue
10 import androidx.compose.runtime.mutableStateOf
11 import androidx.compose.runtime.remember
12 import androidx.compose.runtime.setValue
13 import androidx.compose.ui.Alignment
14 import androidx.compose.ui.Modifier
15 import androidx.compose.ui.geometry.Size
16 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
17 import androidx.compose.ui.graphics.nativeCanvas
18 import androidx.compose.ui.layout.ContentScale
19 import androidx.compose.ui.layout.ScaleFactor
20 import androidx.compose.ui.unit.IntSize
21 import com.airbnb.lottie.AsyncUpdates
22 import com.airbnb.lottie.LottieComposition
23 import com.airbnb.lottie.LottieDrawable
24 import com.airbnb.lottie.RenderMode
25 import kotlin.math.roundToInt
26 
27 /**
28  * This is the base LottieAnimation composable. It takes a composition and renders it at a specific progress.
29  *
30  * The overloaded version of [LottieAnimation] that handles playback and is sufficient for most use cases.
31  *
32  * @param composition The composition that will be rendered. To generate a [LottieComposition], you can use
33  *                    [rememberLottieComposition].
34  * @param progress A provider for the progress (between 0 and 1) that should be rendered. If you want to render a
35  *                         specific frame, you can use [LottieComposition.getFrameForProgress]. In most cases, you will want
36  *                         to use one of the overloaded LottieAnimation composables that drives the animation for you.
37  *                         The overloads that have isPlaying as a parameter instead of progress will drive the
38  *                         animation automatically. You may want to use this version if you want to drive the animation
39  *                         from your own Animatable or via events such as download progress or a gesture.
40  * @param outlineMasksAndMattes Enable this to debug slow animations by outlining masks and mattes.
41  *                              The performance overhead of the masks and mattes will be proportional to the
42  *                              surface area of all of the masks/mattes combined.
43  *                              DO NOT leave this enabled in production.
44  * @param applyOpacityToLayers Sets whether to apply opacity to the each layer instead of shape.
45  *                             Opacity is normally applied directly to a shape. In cases where translucent
46  *                             shapes overlap, applying opacity to a layer will be more accurate at the
47  *                             expense of performance.
48  *                             Note: This process is very expensive. The performance impact will be reduced
49  *                             when hardware acceleration is enabled.
50  * @param enableMergePaths Enables experimental merge paths support. Most animations with merge paths will
51  *                         want this on but merge path support is more limited than some other rendering
52  *                         features so it defaults to off. The only way to know if your animation will work
53  *                         well with merge paths or not is to try it. If your animation has merge paths and
54  *                         doesn't render correctly, please file an issue.
55  * @param renderMode Allows you to specify whether you want Lottie to use hardware or software rendering.
56  *                   Defaults to AUTOMATIC. Refer to [LottieAnimationView.setRenderMode] for more info.
57  * @param maintainOriginalImageBounds When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation,
58  *                                    regardless of the bitmap size.
59  *                                    When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds.
60  *                                    Defaults to false.
61  * @param dynamicProperties Allows you to change the properties of an animation dynamically. To use them, use
62  *                          [rememberLottieDynamicProperties]. Refer to its docs for more info.
63  * @param alignment Define where the animation should be placed within this composable if it has a different
64  *                  size than this composable.
65  * @param contentScale Define how the animation should be scaled if it has a different size than this Composable.
66  * @param clipToCompositionBounds Determines whether or not Lottie will clip the animation to the original animation composition bounds.
67  *                                The composition bounds refers to the Lottie animation composition, not the Compose composition.
68  * @param clipTextToBoundingBox When true, if there is a bounding box set on a text layer (paragraph text), any text
69  *                              that overflows past its height will not be drawn.
70  * @param fontMap A map of keys to Typefaces. The key can be: "fName", "fFamily", or "fFamily-fStyle" as specified in your Lottie file.
71  * @param asyncUpdates When set to true, some parts of animation updates will be done off of the main thread.
72  *                     For more details, refer to the docs of [AsyncUpdates].
73  * @param safeMode If set to true, draw will be wrapped with a try/catch which will cause Lottie to
74  *                     render an empty frame rather than crash your app.
75  */
76 @Composable
77 @JvmOverloads
78 fun LottieAnimation(
79     composition: LottieComposition?,
80     progress: () -> Float,
81     modifier: Modifier = Modifier,
82     outlineMasksAndMattes: Boolean = false,
83     applyOpacityToLayers: Boolean = false,
84     enableMergePaths: Boolean = false,
85     renderMode: RenderMode = RenderMode.AUTOMATIC,
86     maintainOriginalImageBounds: Boolean = false,
87     dynamicProperties: LottieDynamicProperties? = null,
88     alignment: Alignment = Alignment.Center,
89     contentScale: ContentScale = ContentScale.Fit,
90     clipToCompositionBounds: Boolean = true,
91     clipTextToBoundingBox: Boolean = false,
92     fontMap: Map<String, Typeface>? = null,
93     asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
94     safeMode: Boolean = false,
95 ) {
96     val drawable = remember { LottieDrawable() }
97     val matrix = remember { Matrix() }
98     var setDynamicProperties: LottieDynamicProperties? by remember(composition) { mutableStateOf(null) }
99 
100     if (composition == null || composition.duration == 0f) return Box(modifier)
101 
102     val bounds = composition.bounds
103     Canvas(
104         modifier = modifier
105             .lottieSize(bounds.width(), bounds.height())
106     ) {
107         drawIntoCanvas { canvas ->
108             val compositionSize = Size(bounds.width().toFloat(), bounds.height().toFloat())
109             val intSize = IntSize(size.width.roundToInt(), size.height.roundToInt())
110 
111             val scale = contentScale.computeScaleFactor(compositionSize, size)
112             val translation = alignment.align(compositionSize * scale, intSize, layoutDirection)
113             matrix.reset()
114             matrix.preTranslate(translation.x.toFloat(), translation.y.toFloat())
115             matrix.preScale(scale.scaleX, scale.scaleY)
116 
117             drawable.enableMergePathsForKitKatAndAbove(enableMergePaths)
118             drawable.setSafeMode(safeMode)
119             drawable.renderMode = renderMode
120             drawable.asyncUpdates = asyncUpdates
121             drawable.composition = composition
122             drawable.setFontMap(fontMap)
123             if (dynamicProperties !== setDynamicProperties) {
124                 setDynamicProperties?.removeFrom(drawable)
125                 dynamicProperties?.addTo(drawable)
126                 setDynamicProperties = dynamicProperties
127             }
128             drawable.setOutlineMasksAndMattes(outlineMasksAndMattes)
129             drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
130             drawable.maintainOriginalImageBounds = maintainOriginalImageBounds
131             drawable.clipToCompositionBounds = clipToCompositionBounds
132             drawable.clipTextToBoundingBox = clipTextToBoundingBox
133             drawable.progress = progress()
134             drawable.setBounds(0, 0, bounds.width(), bounds.height())
135             drawable.draw(canvas.nativeCanvas, matrix)
136         }
137     }
138 }
139 
140 /**
141  * This is like [LottieAnimation] except that it takes a raw progress parameter instead of taking a progress provider.
142  *
143  * @see LottieAnimation
144  */
145 @Composable
146 @Deprecated("Pass progress as a lambda instead of a float. This overload will be removed in the next release.")
LottieAnimationnull147 fun LottieAnimation(
148     composition: LottieComposition?,
149     @FloatRange(from = 0.0, to = 1.0) progress: Float,
150     modifier: Modifier = Modifier,
151     outlineMasksAndMattes: Boolean = false,
152     applyOpacityToLayers: Boolean = false,
153     enableMergePaths: Boolean = false,
154     renderMode: RenderMode = RenderMode.AUTOMATIC,
155     maintainOriginalImageBounds: Boolean = false,
156     dynamicProperties: LottieDynamicProperties? = null,
157     alignment: Alignment = Alignment.Center,
158     contentScale: ContentScale = ContentScale.Fit,
159     clipToCompositionBounds: Boolean = true,
160     safeMode: Boolean = false,
161     asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
162 ) {
163     LottieAnimation(
164         composition = composition,
165         progress = { progress },
166         modifier = modifier,
167         outlineMasksAndMattes = outlineMasksAndMattes,
168         applyOpacityToLayers = applyOpacityToLayers,
169         enableMergePaths = enableMergePaths,
170         renderMode = renderMode,
171         maintainOriginalImageBounds = maintainOriginalImageBounds,
172         dynamicProperties = dynamicProperties,
173         alignment = alignment,
174         contentScale = contentScale,
175         clipToCompositionBounds = clipToCompositionBounds,
176         asyncUpdates = asyncUpdates,
177         safeMode = safeMode
178     )
179 }
180 
181 /**
182  * This is like [LottieAnimation] except that it handles driving the animation via [animateLottieCompositionAsState]
183  * instead of taking a progress provider.
184  *
185  * @see LottieAnimation
186  * @see animateLottieCompositionAsState
187  */
188 @Composable
189 @JvmOverloads
LottieAnimationnull190 fun LottieAnimation(
191     composition: LottieComposition?,
192     modifier: Modifier = Modifier,
193     isPlaying: Boolean = true,
194     restartOnPlay: Boolean = true,
195     clipSpec: LottieClipSpec? = null,
196     speed: Float = 1f,
197     iterations: Int = 1,
198     outlineMasksAndMattes: Boolean = false,
199     applyOpacityToLayers: Boolean = false,
200     enableMergePaths: Boolean = false,
201     renderMode: RenderMode = RenderMode.AUTOMATIC,
202     reverseOnRepeat: Boolean = false,
203     maintainOriginalImageBounds: Boolean = false,
204     dynamicProperties: LottieDynamicProperties? = null,
205     alignment: Alignment = Alignment.Center,
206     contentScale: ContentScale = ContentScale.Fit,
207     clipToCompositionBounds: Boolean = true,
208     clipTextToBoundingBox: Boolean = false,
209     fontMap: Map<String, Typeface>? = null,
210     safeMode: Boolean = false,
211     asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
212 ) {
213     val progress by animateLottieCompositionAsState(
214         composition,
215         isPlaying,
216         restartOnPlay,
217         reverseOnRepeat,
218         clipSpec,
219         speed,
220         iterations,
221     )
222     LottieAnimation(
223         composition = composition,
224         progress = { progress },
225         modifier = modifier,
226         outlineMasksAndMattes = outlineMasksAndMattes,
227         applyOpacityToLayers = applyOpacityToLayers,
228         enableMergePaths = enableMergePaths,
229         renderMode = renderMode,
230         maintainOriginalImageBounds = maintainOriginalImageBounds,
231         dynamicProperties = dynamicProperties,
232         alignment = alignment,
233         contentScale = contentScale,
234         clipToCompositionBounds = clipToCompositionBounds,
235         clipTextToBoundingBox = clipTextToBoundingBox,
236         fontMap = fontMap,
237         asyncUpdates = asyncUpdates,
238         safeMode = safeMode
239     )
240 }
241 
timesnull242 private operator fun Size.times(scale: ScaleFactor): IntSize {
243     return IntSize((width * scale.scaleX).toInt(), (height * scale.scaleY).toInt())
244 }
245