0
# Animated Images
1
2
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.
3
4
## Capabilities
5
6
### AnimatedImage Type
7
8
Core animated image type with platform-specific implementations.
9
10
```kotlin { .api }
11
/**
12
* Platform-specific animated image representation
13
* Handles frame-based animations with proper timing and looping
14
*/
15
expect class AnimatedImage
16
```
17
18
**Platform Notes:**
19
- **Desktop**: Uses Skia `Codec` for efficient frame-based animation
20
- **Other platforms**: Platform-specific implementations for optimal performance
21
22
### Image Loading Functions
23
24
Load animated images from various sources with automatic format detection.
25
26
```kotlin { .api }
27
/**
28
* Load an animated image from a path (network URL or local file)
29
* Automatically detects if the path is a network URL or local file path
30
* @param path URL or file path to the animated image
31
* @return AnimatedImage instance ready for animation
32
*/
33
expect suspend fun loadAnimatedImage(path: String): AnimatedImage
34
35
/**
36
* Load an animated image from application resources
37
* @param path Resource path within the application bundle
38
* @return AnimatedImage instance ready for animation
39
*/
40
expect suspend fun loadResourceAnimatedImage(path: String): AnimatedImage
41
```
42
43
**Usage Examples:**
44
45
```kotlin
46
import org.jetbrains.compose.animatedimage.*
47
48
@Composable
49
fun AnimatedImageDemo() {
50
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
51
52
// Load from network
53
LaunchedEffect(Unit) {
54
animatedImage = loadAnimatedImage("https://example.com/animation.gif")
55
}
56
57
// Load from resources
58
LaunchedEffect(Unit) {
59
animatedImage = loadResourceAnimatedImage("animations/spinner.gif")
60
}
61
62
animatedImage?.let { image ->
63
Image(
64
bitmap = image.animate(),
65
contentDescription = "Animated GIF",
66
modifier = Modifier.size(200.dp)
67
)
68
}
69
}
70
```
71
72
### Animation Playback
73
74
Convert animated images to displayable bitmaps with automatic frame progression.
75
76
```kotlin { .api }
77
/**
78
* Animate the loaded image, returning the current frame as ImageBitmap
79
* Handles frame timing and looping automatically
80
* @return Current frame as ImageBitmap for display in Image composable
81
*/
82
@Composable
83
expect fun AnimatedImage.animate(): ImageBitmap
84
```
85
86
**Usage Examples:**
87
88
```kotlin
89
@Composable
90
fun LoadingSpinner() {
91
var spinner by remember { mutableStateOf<AnimatedImage?>(null) }
92
93
LaunchedEffect(Unit) {
94
spinner = loadResourceAnimatedImage("loading-spinner.gif")
95
}
96
97
Box(
98
modifier = Modifier.fillMaxSize(),
99
contentAlignment = Alignment.Center
100
) {
101
spinner?.let {
102
Image(
103
bitmap = it.animate(), // Automatically progresses through frames
104
contentDescription = "Loading...",
105
modifier = Modifier.size(48.dp)
106
)
107
} ?: CircularProgressIndicator() // Fallback while loading
108
}
109
}
110
```
111
112
### Utility Extensions
113
114
Additional utilities for working with animated images.
115
116
```kotlin { .api }
117
/**
118
* A blank 1x1 pixel ImageBitmap for placeholder usage
119
*/
120
val ImageBitmap.Companion.Blank: ImageBitmap
121
```
122
123
**Usage Examples:**
124
125
```kotlin
126
@Composable
127
fun AnimatedImageWithPlaceholder(imageUrl: String) {
128
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
129
var isLoading by remember { mutableStateOf(true) }
130
131
LaunchedEffect(imageUrl) {
132
isLoading = true
133
try {
134
animatedImage = loadAnimatedImage(imageUrl)
135
} catch (e: Exception) {
136
// Handle loading error
137
} finally {
138
isLoading = false
139
}
140
}
141
142
Image(
143
bitmap = when {
144
isLoading -> ImageBitmap.Blank
145
animatedImage != null -> animatedImage!!.animate()
146
else -> ImageBitmap.Blank // Error state
147
},
148
contentDescription = "Animated image",
149
modifier = Modifier.size(150.dp)
150
)
151
}
152
```
153
154
### Advanced Usage Patterns
155
156
#### Conditional Animation
157
158
```kotlin
159
@Composable
160
fun ConditionalAnimation(shouldAnimate: Boolean) {
161
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
162
var staticImage by remember { mutableStateOf<ImageBitmap?>(null) }
163
164
LaunchedEffect(Unit) {
165
animatedImage = loadAnimatedImage("animation.gif")
166
staticImage = loadAnimatedImage("static-frame.png").animate()
167
}
168
169
Image(
170
bitmap = if (shouldAnimate) {
171
animatedImage?.animate() ?: ImageBitmap.Blank
172
} else {
173
staticImage ?: ImageBitmap.Blank
174
},
175
contentDescription = "Conditional animation",
176
modifier = Modifier.size(100.dp)
177
)
178
}
179
```
180
181
#### Animation Gallery
182
183
```kotlin
184
@Composable
185
fun AnimationGallery() {
186
val animationUrls = listOf(
187
"https://example.com/cat.gif",
188
"https://example.com/dog.gif",
189
"https://example.com/bird.gif"
190
)
191
192
LazyRow {
193
items(animationUrls) { url ->
194
AnimatedImageCard(url)
195
}
196
}
197
}
198
199
@Composable
200
fun AnimatedImageCard(url: String) {
201
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
202
203
Card(
204
modifier = Modifier
205
.size(120.dp)
206
.padding(8.dp)
207
) {
208
LaunchedEffect(url) {
209
animatedImage = loadAnimatedImage(url)
210
}
211
212
Box(
213
modifier = Modifier.fillMaxSize(),
214
contentAlignment = Alignment.Center
215
) {
216
animatedImage?.let { image ->
217
Image(
218
bitmap = image.animate(),
219
contentDescription = null,
220
modifier = Modifier.fillMaxSize(),
221
contentScale = ContentScale.Crop
222
)
223
} ?: CircularProgressIndicator(modifier = Modifier.size(24.dp))
224
}
225
}
226
}
227
```
228
229
### Error Handling
230
231
```kotlin
232
@Composable
233
fun RobustAnimatedImage(imagePath: String) {
234
var animatedImage by remember { mutableStateOf<AnimatedImage?>(null) }
235
var error by remember { mutableStateOf<String?>(null) }
236
var isLoading by remember { mutableStateOf(true) }
237
238
LaunchedEffect(imagePath) {
239
isLoading = true
240
error = null
241
try {
242
animatedImage = loadAnimatedImage(imagePath)
243
} catch (e: Exception) {
244
error = "Failed to load animation: ${e.message}"
245
animatedImage = null
246
} finally {
247
isLoading = false
248
}
249
}
250
251
Box(
252
modifier = Modifier.size(200.dp),
253
contentAlignment = Alignment.Center
254
) {
255
when {
256
isLoading -> CircularProgressIndicator()
257
error != null -> Text(
258
error!!,
259
color = MaterialTheme.colors.error,
260
textAlign = TextAlign.Center
261
)
262
animatedImage != null -> Image(
263
bitmap = animatedImage!!.animate(),
264
contentDescription = "Animation",
265
modifier = Modifier.fillMaxSize()
266
)
267
else -> Text("No image")
268
}
269
}
270
}
271
```
272
273
### Performance Considerations
274
275
- **Automatic Format Detection**: The system automatically detects image formats and optimizes loading
276
- **Frame Timing**: Animation respects original frame timing for smooth playback
277
- **Memory Management**: Platform-specific implementations optimize memory usage
278
- **Network Loading**: Supports both local and network image sources seamlessly
279
- **Infinite Looping**: Animations loop continuously without manual intervention
280
281
### Platform Implementation Details
282
283
#### Desktop Implementation
284
- Uses Skia `Codec` for efficient frame decoding
285
- Supports GIF, WebP, and other animated formats
286
- Automatic network URL detection
287
- Loaders: `NetworkAnimatedImageLoader`, `LocalAnimatedImageLoader`, `ResourceAnimatedImageLoader`
288
289
#### Multiplatform Support
290
- Each platform provides optimized implementations
291
- Consistent API across all supported platforms
292
- Platform-specific performance optimizations