Compose Multiplatform Adobe After Effects Bodymovin (Lottie) animations rendering engine.
Important
Starting from v2.0 Compottie has its own multiplatform rendering engine without any platform delegates.
The new engine is implemented from scratch and therefore may have bugs.
Please report if you find any, preferably with a reproducible animation.
List of supported AE Lottie features can be found here
Module | Description |
---|---|
compottie |
Main module with rendering engine and JsonString animation spec. Currently has two branches - 1.x (with platform renderers - Skottie and lottie-android) and 2.x (with own renderer). 1.x is maintained until the new renderer becomes stable |
compottie-dot |
Contains dotLottie and ZIP animation spec. For Compottie 2.x only |
compottie-network |
Contains Url animation spec and asset/font managers (with Ktor3 and local cache with Okio). Allows loading animations and assets from web. For Compottie 2.x only |
compottie-network-core |
Contains base HttpClient-free implementations for network module. Allows to specify custom HTTP client (Ktor3 or any other). |
compottie-resources |
Contains asset and font managers powered by official Compose resources. For Compottie 2.x only |
Add as a Gradle dependency
dependencies {
implementation("io.github.alexzhirkevich:compottie:<version>")
implementation("io.github.alexzhirkevich:compottie-dot:<version>")
implementation("io.github.alexzhirkevich:compottie-network:<version>")
implementation("io.github.alexzhirkevich:compottie-resources:<version>")
}
[versions]
compottie="<version>"
[libraries]
compottie = { module = "io.github.alexzhirkevich:compottie", version.ref = "compottie" }
compottie-dot = { module = "io.github.alexzhirkevich:compottie-dot", version.ref = "compottie" }
compottie-network = { module = "io.github.alexzhirkevich:compottie-network", version.ref = "compottie" }
compottie-resources = { module = "io.github.alexzhirkevich:compottie-resources", version.ref = "compottie" }
Note
For projects with Android minSdk < 26 dot
and network
modules require desugaring
The following docs describe the Compottie 2.x usage. For Compottie 1.x docs please refer to the airbnb docs.
- Basic Usage
- LottieComposition
- Animating/Updating Progress
- LottieAnimatable
- dotLottie (ZIP)
- Images
- Fonts
- URL loading
- Dynamic Properties
val composition by rememberLottieComposition {
LottieCompositionSpec.JsonString(
Res.readBytes("files/anim.json").decodeToString()
)
}
val progress by animateLottieCompositionAsState(composition)
Image(
painter = rememberLottiePainter(
composition = composition,
progress = { progress },
),
contentDescription = "Lottie animation"
)
Or with the rememberLottiePainter
overload that merges rememberLottiePainter
and animateLottieCompositionsState()
val composition by rememberLottieComposition {
LottieCompositionSpec.JsonString(
Res.readBytes("files/anim.json").decodeToString()
)
}
Image(
painter = rememberLottiePainter(
composition = composition,
iterations = Compottie.IterateForever
),
contentDescription = "Lottie animation"
)
LottieComposition
is the parsed version of your Lottie json file. It is stateless and can be
cached/reused freely. Call rememberLottieComposition(spec)
to create new composition.
LottieCompositionSpec
is an open interface that lets you select the animation source (string/zip, network/assets, etc.).
For example:
val animFromJsonRes by rememberLottieComposition {
LottieCompositionSpec.JsonString(
Res.readBytes("files/anim.json").decodeToString()
)
}
val animFromUrl by rememberLottieComposition {
LottieCompositionSpec.Url("https://example.com/anim.lotie")
}
val animFromArchiveRes by rememberLottieComposition {
LottieCompositionSpec.DotLottie(
Res.readBytes("files/anim.lottie")
)
}
The type returned from rememberLottieComposition
is
interface LottieCompositionResult : State<LottieComposition?>
This allows you to use it in two ways:
val composition: LottieComposition? by rememberLottieComposition(spec)
This will return null until the composition is parsed and then will return the LottieComposition
object.
Use this version in most cases, especially if you don't need any of the extra functionality on LottieCompositionResult
.
val compositionResult: LottieCompositionResult = rememberLottieComposition(spec)
LottieCompositionResult
lets you:
- Access the composition via
compositionResult.value
- Access
error
,isLoading
,isComplete
,isFailure
, andisSuccess
properties. - Call
await()
to await the parsed composition from a coroutine.
You have the option of handling progress entirely yourself. If you choose to do that, just pass in progress
to your LottieAnimation
composable.
In most cases, you will want to use either animateLottieCompositionAsState()
or LottieAnimatable
. These APIs were designed to be analogous to the standard Jetpack Compose APIs. animateLottieCompositionAsState
is analogous to animate*AsState and LottieAnimatable
is analogous to Animatable.
The decision for whether to use one over the other is similar as well:
- If your animation is very simple or a function of other state properties, use
animateLottieCompositionAsState()
. - If you need to imperatively call
animate
orsnapTo
from something like aLaunchedEffect
then useLottieAnimatable
.
animateLottieCompositionAsState()
returns and LottieAnimatable
implements:
interface LottieAnimationState : State<Float>
val progress by animateLottieCompositionAsState(composition)
val progress by animateLottieCompositionAsState(
composition,
iterations = Compottie.IterateForever,
)
val progress by animateLottieCompositionAsState(
composition,
clipSpec = LottieClipSpec.Progress(0.5f, 0.75f),
)
@Stable
class MyHoistedState {
val lottieAnimatable = LottieAnimatable()
val somethingElse by mutableStateOf(0f)
}
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.animation))
val lottieAnimatable = rememberLottieAnimatable()
LaunchedEffect(Unit) {
lottieAnimatable.animate(
composition,
iterations = Compottie.IterateForever,
clipSpec = LottieClipSpec.Progress(0.5f, 0.75f),
)
}
dotLottie is an open-source file format that aggregates one or more Lottie files and their associated resources into a single file. They are ZIP archives compressed with the Deflate compression method and carry the file extension of ".lottie".
dotLottie animations are up to 10x smaller in size and can have auto-linked bundled assets (as well as external assets ofc).
compottie-dot
module is required to use dotLottie animations in your app. It brings the new type of composition spec - LottieCompositionSpec.DotLottie
.
You can also use DotLottie
composition spec even without making a .lottie file just by ZIPping
your animation with assets using deflate algorithm. The only limitation - animation can't be named
as "manifest.json" and should contain exactly one .json file.
The DotLottie
composition spec works both with dotLottie and simple ZIP files.
For dotLottie files you can specify the animation to run if archive contains multiple animations
fun LottieCompositionSpec.Companion.DotLottie(
archive: ByteArray,
animationId: String?
) : LottieCompositionSpec
Images should be avoided whenever possible. They are much larger, less performant, and can lead to pixelation. Whenever possible, try and make your animation consist solely of vectors. However, Lottie does support images in one of 4 ways:
- Baked into the Lottie json file. This is done via an option in the exporter (such as teh Bodymovin After Effects plugin). When done, images are encoded as a base64 string and embedded directly in the json file. This is the simplest way to use images because everything is contained in a single file and no additional work is necessary to make them work;
- Zipped with the json file in a single zip file. When parsing the animation, Lottie will unzip the animation and automatically link any images in zip file to the composition. These zip files can be stored in assets and loaded via
LottieCompositionSpec.DotLottie
(requirescompottie-dot
module) or downloaded via the internet and loaded viaLottieCompositionSpec.Url
; - External images provided by
LottieAssetsManager
; - Via dynamic properties.
LottieAssetsManager
should be passed to rememberLottieComposition
to load external resources.
compottie-resources
provides ready-to-use implementation that loads assets from compose-resources:
val painter = rememberLottiePainter(
composition = composition,
assetsManager = rememberResourcesAssetsManager(
directory = "files", // by default
readBytes = Res::readBytes
)
)
Text can be drawn in 2 ways: using fonts and using glyphs (when characters are baked to the animation as lottie shapes)
LottieFontManager
should be passed to rememberLottiePainter
to use custom fonts.
compottie-resources
provides ready-to-use implementation that loads fonts from compose-resources:
val painter = rememberLottiePainter(
composition = composition,
fontManager = rememberResourcesFontManager { fontSpec ->
when (fontSpec.family) {
"Comic Neue" -> Res.font.ComicNeue
else -> null // default font will be used
}
}
)
To load images remotely compottie-network
module should be added as a dependensy.
This module brings an additional composition spec called LottieCompositionSpec.Url
fun LottieCompositionSpec.Companion.Url(
url : String,
format: LottieAnimationFormat,
client: HttpClient,
request : NetworkRequest,
cacheStrategy: LottieCacheStrategy,
)
that can be used to load JSON and dotLottie animations from the Internet.
LottieAnimationFormat
is used to determine wheither animation is a JSON or dotLottie. If you left it Undefined
,
composition spec will automatically detect is this a JSON or dotLottie file.
Ktor HTTP client can be provided with client
parameter.
Caching strategy can be set with cacheStrategy
parameter. By default animations are cached in the
device temp directory.
The network module also brings the NetworkAssetsManager
that have similar parameters and can be used to load image assets.
If you are using Url composition spec then specifying NetworkAssetsManager
is redundant.
Url composition spec automatically prepares url assets
Lottie allows you to update animation properties at runtime. Some reasons you may want to do this are:
- Change colors for day/night or other app theme.
- Localize animation text
- Change the progress of a specific layer to show download progress.
- Change the size and position of something in response to a gesture.
Dynamic properties are created with rememberLottieDynamicProperties
val painter = rememberLottiePainter(
composition = composition,
dynamicProperties = rememberLottieDynamicProperties {
shapeLayer("Precomposition 1", "Shape Layer 4") {
transform {
rotation { current -> current * progress }
}
fill("Group 1", "Fill 4") {
color { Color.Red }
alpha { .5f }
}
group("Group 4") {
ellipse("Ellipse 1") {
// configure size, position of the ellipse named "Ellipse 1"
}
stroke("Ellipse 1 Stroke") {
// configure stroke named "Ellipse 1 Stroke" in the same group
}
}
}
}
)
You can also use wildcards for declaring dynamic properties, where **
is any level deep wildcard
and *
is one level deep.
val painter = rememberLottiePainter(
composition = composition,
dynamicProperties = rememberLottieDynamicProperties {
// for each layer named 'Shape Layer 4' on any level deep
shapeLayer("**", "Shape Layer 4") {
// for each fill named 'Fill 4' on the 2nd level deep
fill("*", "Fill 4") {
color { Color.Red }
alpha { .5f }
}
}
}
)
Note, that final property building blocks (such as rotations, color, alpha) are called on EACH ANIMATION FRAME and should be cached if they don't rely on progress and have allocations or hard computations.
More info about dynamic properties for those who is not familiar with AE / Lottie format can be found here.