blob: 7c30b6c74990dca244d133aa5cdae074c005588d [file] [log] [blame]
package com.airbnb.lottie.compose
import androidx.annotation.FloatRange
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.platform.LocalContext
import com.airbnb.lottie.ImageAssetDelegate
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieDrawable
import com.airbnb.lottie.manager.ImageAssetManager
import com.airbnb.lottie.setImageAssetManager
/**
* This is the base LottieAnimation composable. It takes a composition and renders it at a specific progress.
*
* There are overloaded versions of this that allow you to pass in raw [LottieCompositionSpec] or handle
* basic animations. Refer to the documentation for the overloaded versions for more info.
*
* @param composition The composition that will be rendered. To generate a [LottieComposition], you can use
* [lottieComposition] or use the overloaded version that takes a
* [LottieCompositionSpec] instead of the [LottieComposition] itself. You should only need
* to use [lottieComposition] directly if you explicitly need to handle failure
* states which won't occur in most cases if your animation is packaged within your app.
* @param progress The progress (between 0 and 1) that should be rendered. If you want to render a specific
* frame, you can use [LottieComposition.getFrameForProgress]. In most cases, you will want
* to use one of th overloaded LottieAnimation composables that drives the animation for you.
* The overloads that have isPlaying as a parameter instead of progress will drive the
* animation automatically. You may want to use this version if you want to drive the animation
* from your own Animatable or via events such as download progress or a gesture.
* @param imageAssetsFolder If you use image assets, you must explicitly specify the folder in assets/ in which
* they are located because bodymovin uses the name filenames across all
* compositions (img_#). Do NOT rename the images themselves.
* If your images are located in src/main/assets/airbnb_loader/ then imageAssetsFolder
* should be set to "airbnb_loader"
* Be wary if you are using many images, however. Lottie is designed to work with
* vector shapes from After Effects. If your images look like they could be
* represented with vector shapes, see if it is possible to convert them to shape
* layers and re-export your animation. Check the documentation at
* http://airbnb.io/lottie for more information about importing shapes from Sketch
* or Illustrator to avoid this.
* @param imageAssetDelegate Use this if you can't bundle images with your app. This may be useful if you
* download the animations from the network or have the images saved to an SD Card.
* In that case, Lottie will defer the loading of the bitmap to this delegate.
* Be wary if you are using many images, however. Lottie is designed to work with
* vector shapes from After Effects. If your images look like they could be
* represented with vector shapes, see if it is possible to convert them to shape
* layers and re-export your animation. Check the documentation at
* http://airbnb.io/lottie for more information about importing shapes from Sketch
* or Illustrator to avoid this.
* @param outlineMasksAndMattes Enable this to debug slow animations by outlining masks and mattes.
* The performance overhead of the masks and mattes will be proportional to the
* surface area of all of the masks/mattes combined.
* DO NOT leave this enabled in production.
* @param applyOpacityToLayers Sets whether to apply opacity to the each layer instead of shape.
* Opacity is normally applied directly to a shape. In cases where translucent
* shapes overlap, applying opacity to a layer will be more accurate at the
* expense of performance.
* Note: This process is very expensive. The performance impact will be reduced
* when hardware acceleration is enabled.
* @param enableMergePaths Enables experimental merge paths support. Most animations with merge paths will
* want this on but merge path support is more limited than some other rendering
* features so it defaults to off. The only way to know if your animation will work
* well with merge paths or not is to try it. If your animation has merge paths and
* doesn't render correctly, please file an issue.
*/
@Composable
fun LottieAnimation(
composition: LottieComposition?,
@FloatRange(from = 0.0, to = 1.0) progress: Float,
modifier: Modifier = Modifier,
imageAssetsFolder: String? = null,
imageAssetDelegate: ImageAssetDelegate? = null,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
enableMergePaths: Boolean = false,
) {
val drawable = remember { LottieDrawable() }
var imageAssetManager by remember { mutableStateOf<ImageAssetManager?>(null) }
if (composition == null || composition.duration == 0f) return Box(modifier)
if (composition.hasImages()) {
val context = LocalContext.current
LaunchedEffect(context, composition, imageAssetsFolder, imageAssetDelegate) {
imageAssetManager = ImageAssetManager(context, imageAssetsFolder, imageAssetDelegate, composition.images)
}
} else {
imageAssetManager = null
}
Canvas(
modifier = modifier
.maintainAspectRatio(composition)
) {
drawIntoCanvas { canvas ->
withTransform({
scale(size.width / composition.bounds.width().toFloat(), size.height / composition.bounds.height().toFloat(), Offset.Zero)
}) {
drawable.composition = composition
drawable.setOutlineMasksAndMattes(outlineMasksAndMattes)
drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
drawable.enableMergePathsForKitKatAndAbove(enableMergePaths)
drawable.setImageAssetManager(imageAssetManager)
drawable.progress = progress
drawable.draw(canvas.nativeCanvas)
}
}
}
}
/**
* This is like [LottieAnimation] except that it takes a [LottieCompositionSpec] instead of the
* [LottieComposition] itself. This will be more convenient for most use cases unless you
* need to explicitly check for failure states while loading the composition, which is done
* via [lottieComposition].
*
* @see LottieAnimation
*/
@Composable
fun LottieAnimation(
compositionSpec: LottieCompositionSpec,
progress: Float,
modifier: Modifier = Modifier,
imageAssetsFolder: String? = null,
imageAssetDelegate: ImageAssetDelegate? = null,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
enableMergePaths: Boolean = false,
) {
val composition by lottieComposition(compositionSpec)
LottieAnimation(
composition,
progress,
modifier,
imageAssetsFolder,
imageAssetDelegate,
outlineMasksAndMattes,
applyOpacityToLayers,
enableMergePaths,
)
}
/**
* This is like [LottieAnimation] except that it both takes a [LottieCompositionSpec] instead of a
* [LottieComposition] and also handles driving the animation via [animateLottieComposition] instead
* of taking a raw progress parameter.
*
* @see LottieAnimation
* @see animateLottieComposition
*/
@Composable
fun LottieAnimation(
compositionSpec: LottieCompositionSpec,
modifier: Modifier = Modifier,
isPlaying: Boolean = true,
restartOnPlay: Boolean = true,
repeatCount: Int = 1,
clipSpec: LottieClipSpec? = null,
speed: Float = 1f,
onRepeat: ((repeatCount: Int) -> Unit)? = null,
onFinished: (() -> Unit)? = null,
imageAssetsFolder: String? = null,
imageAssetDelegate: ImageAssetDelegate? = null,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
enableMergePaths: Boolean = false,
) {
val composition by lottieComposition(compositionSpec)
LottieAnimation(
composition,
modifier,
isPlaying,
restartOnPlay,
clipSpec,
speed,
repeatCount,
onRepeat,
onFinished,
imageAssetsFolder,
imageAssetDelegate,
outlineMasksAndMattes,
applyOpacityToLayers,
enableMergePaths,
)
}
/**
* This is like [LottieAnimation] except that it handles driving the animation via [animateLottieComposition]
* instead of taking a raw progress parameter.
*
* @see LottieAnimation
* @see animateLottieComposition
*/
@Composable
fun LottieAnimation(
composition: LottieComposition?,
modifier: Modifier = Modifier,
isPlaying: Boolean = true,
restartOnPlay: Boolean = true,
clipSpec: LottieClipSpec? = null,
speed: Float = 1f,
repeatCount: Int = 1,
onRepeat: ((repeatCount: Int) -> Unit)? = null,
onFinished: (() -> Unit)? = null,
imageAssetsFolder: String? = null,
imageAssetDelegate: ImageAssetDelegate? = null,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
enableMergePaths: Boolean = false,
) {
val progress by animateLottieComposition(
composition,
isPlaying,
restartOnPlay,
clipSpec,
speed,
repeatCount,
onRepeat,
onFinished,
)
LottieAnimation(
composition,
progress,
modifier,
imageAssetsFolder,
imageAssetDelegate,
outlineMasksAndMattes,
applyOpacityToLayers,
enableMergePaths,
)
}
private fun Modifier.maintainAspectRatio(composition: LottieComposition?): Modifier {
composition ?: return this
return this.then(aspectRatio(composition.bounds.width() / composition.bounds.height().toFloat()))
}