Additional UI components for Compose Multiplatform including SplitPane layouts, resource loading, animated images, and UI tooling preview support
—
Cross-platform animated image support with automatic format detection and platform-optimized loading for GIFs and other animated formats, providing seamless animation playback in Compose applications.
Core animated image type with platform-specific implementations.
/**
* Platform-specific animated image representation
* Handles frame-based animations with proper timing and looping
*/
expect class AnimatedImagePlatform Notes:
Codec for efficient frame-based animationLoad animated images from various sources with automatic format detection.
/**
* Load an animated image from a path (network URL or local file)
* Automatically detects if the path is a network URL or local file path
* @param path URL or file path to the animated image
* @return AnimatedImage instance ready for animation
*/
expect suspend fun loadAnimatedImage(path: String): AnimatedImage
/**
* Load an animated image from application resources
* @param path Resource path within the application bundle
* @return AnimatedImage instance ready for animation
*/
expect suspend fun loadResourceAnimatedImage(path: String): AnimatedImageUsage Examples:
import org.jetbrains.compose.animatedimage.*
@Composable
fun AnimatedImageDemo() {
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
// Load from network
LaunchedEffect(Unit) {
animatedImage = loadAnimatedImage("https://example.com/animation.gif")
}
// Load from resources
LaunchedEffect(Unit) {
animatedImage = loadResourceAnimatedImage("animations/spinner.gif")
}
animatedImage?.let { image ->
Image(
bitmap = image.animate(),
contentDescription = "Animated GIF",
modifier = Modifier.size(200.dp)
)
}
}Convert animated images to displayable bitmaps with automatic frame progression.
/**
* Animate the loaded image, returning the current frame as ImageBitmap
* Handles frame timing and looping automatically
* @return Current frame as ImageBitmap for display in Image composable
*/
@Composable
expect fun AnimatedImage.animate(): ImageBitmapUsage Examples:
@Composable
fun LoadingSpinner() {
var spinner by remember { mutableStateOf<AnimatedImage?>(null) }
LaunchedEffect(Unit) {
spinner = loadResourceAnimatedImage("loading-spinner.gif")
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
spinner?.let {
Image(
bitmap = it.animate(), // Automatically progresses through frames
contentDescription = "Loading...",
modifier = Modifier.size(48.dp)
)
} ?: CircularProgressIndicator() // Fallback while loading
}
}Additional utilities for working with animated images.
/**
* A blank 1x1 pixel ImageBitmap for placeholder usage
*/
val ImageBitmap.Companion.Blank: ImageBitmapUsage Examples:
@Composable
fun AnimatedImageWithPlaceholder(imageUrl: String) {
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(imageUrl) {
isLoading = true
try {
animatedImage = loadAnimatedImage(imageUrl)
} catch (e: Exception) {
// Handle loading error
} finally {
isLoading = false
}
}
Image(
bitmap = when {
isLoading -> ImageBitmap.Blank
animatedImage != null -> animatedImage!!.animate()
else -> ImageBitmap.Blank // Error state
},
contentDescription = "Animated image",
modifier = Modifier.size(150.dp)
)
}@Composable
fun ConditionalAnimation(shouldAnimate: Boolean) {
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
var staticImage by remember { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(Unit) {
animatedImage = loadAnimatedImage("animation.gif")
staticImage = loadAnimatedImage("static-frame.png").animate()
}
Image(
bitmap = if (shouldAnimate) {
animatedImage?.animate() ?: ImageBitmap.Blank
} else {
staticImage ?: ImageBitmap.Blank
},
contentDescription = "Conditional animation",
modifier = Modifier.size(100.dp)
)
}@Composable
fun AnimationGallery() {
val animationUrls = listOf(
"https://example.com/cat.gif",
"https://example.com/dog.gif",
"https://example.com/bird.gif"
)
LazyRow {
items(animationUrls) { url ->
AnimatedImageCard(url)
}
}
}
@Composable
fun AnimatedImageCard(url: String) {
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
Card(
modifier = Modifier
.size(120.dp)
.padding(8.dp)
) {
LaunchedEffect(url) {
animatedImage = loadAnimatedImage(url)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
animatedImage?.let { image ->
Image(
bitmap = image.animate(),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
} ?: CircularProgressIndicator(modifier = Modifier.size(24.dp))
}
}
}@Composable
fun RobustAnimatedImage(imagePath: String) {
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
var error by remember { mutableStateOf<String?>(null) }
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(imagePath) {
isLoading = true
error = null
try {
animatedImage = loadAnimatedImage(imagePath)
} catch (e: Exception) {
error = "Failed to load animation: ${e.message}"
animatedImage = null
} finally {
isLoading = false
}
}
Box(
modifier = Modifier.size(200.dp),
contentAlignment = Alignment.Center
) {
when {
isLoading -> CircularProgressIndicator()
error != null -> Text(
error!!,
color = MaterialTheme.colors.error,
textAlign = TextAlign.Center
)
animatedImage != null -> Image(
bitmap = animatedImage!!.animate(),
contentDescription = "Animation",
modifier = Modifier.fillMaxSize()
)
else -> Text("No image")
}
}
}Codec for efficient frame decodingNetworkAnimatedImageLoader, LocalAnimatedImageLoader, ResourceAnimatedImageLoaderInstall with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-compose-components--components