svga_next
A Flutter package for playing SVGA animations. It loads animations from assets, files, URLs, or bytes, and supports playback controls, dynamic content, and optional audio.
SVGA parsing runs in a background isolate. The package decodes images before playback starts and caches loaded animations in memory.
Requirements
- Flutter 3.41 or later
- Dart 3.3 or later
- A Flutter platform that supports
dart:io. Web is not supported.
Install
Add the package to your app's pubspec.yaml:
dependencies:
svga_next: ^0.1.5
Then run flutter pub get.
Play an animation
Declare an SVGA file in your app's pubspec.yaml:
flutter:
assets:
- assets/animation.svga
Pass the asset to SvgaPlayer:
import 'package:flutter/material.dart';
import 'package:svga_next/svga_next.dart';
class AnimationView extends StatelessWidget {
const AnimationView({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 300,
height: 300,
child: SvgaPlayer(
source: const SvgaSource.asset('assets/animation.svga'),
onFinished: () => debugPrint('Animation finished'),
),
);
}
}
SvgaPlayer loads and plays the animation when it enters the widget tree. It plays once by default. Set isLoop: true to repeat indefinitely, or set loops to a specific play count. Use placeholder and errorBuilder to show a widget while loading or after a load error.
Load from another source
SvgaSource accepts a network URL, local file path, or byte array:
SvgaSource.network('https://example.com/animation.svga');
SvgaSource.file('/path/to/animation.svga');
SvgaSource.file('/cache/animation.svga', cacheKey: 'animation-url');
SvgaSource.memory(bytes);
For network requests, pass headers to SvgaSource.network. To use your own HTTP client, download the bytes yourself and pass them to SvgaSource.memory. HttpOverrides.global does not apply to the package's background isolate.
File sources use file:<path> as their cache key by default. Pass cacheKey to use file:<cacheKey> instead, for example when a download moves between local paths. File sources with the same effective key compare equal and share a movie when their decode options match. Reuse a key only for the same animation. Memory sources accept an optional cacheKey too; without one, each load is uncached.
Control playback
For play, pause, resume, seek, and speed controls, create an SvgaController in a State class that provides TickerProvider:
class ControlledAnimation extends StatefulWidget {
const ControlledAnimation({super.key});
@override
State<ControlledAnimation> createState() => _ControlledAnimationState();
}
class _ControlledAnimationState extends State<ControlledAnimation>
with SingleTickerProviderStateMixin {
late final SvgaController controller = SvgaController(vsync: this);
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SvgaPlayer(
source: const SvgaSource.asset('assets/animation.svga'),
controller: controller,
autoPlay: false,
onLoaded: (_) {
controller.play(loops: 1);
},
);
}
}
Call controller.pause(), resume(), stop(), or seekTo(frame) as needed. Set controller.speed, volume, or muted to change playback. play(from: startFrame, to: endFrame, loops: count) plays a frame range; to is exclusive, and loops: 0 repeats indefinitely.
Replace content at runtime
Use SvgaDynamicEntity to replace a layer's image or text, draw into a layer, or hide it. Keys are the sprite image keys stored in the SVGA file.
final dynamicEntity = SvgaDynamicEntity();
dynamicEntity.setText(
'name',
text: 'Alex',
style: const TextStyle(color: Colors.white),
);
dynamicEntity.setImageProvider(
'avatar',
provider: const AssetImage('assets/avatar.png'),
circle: true,
);
SvgaPlayer(
source: const SvgaSource.asset('assets/animation.svga'),
dynamicEntity: dynamicEntity,
);
Dispose of the entity when its owner is removed. setImage, setTextSpan, setDrawer, and setHidden provide other replacement options. SvgaPlayer waits up to 1.5 seconds for pending setImageProvider calls before autoplay starts. Set waitForDynamicImages to change that limit.
Call movie.layoutSizeOf('avatar') to size a replacement image for a slot. It returns the first matching sprite's first visible frame with positive width and height, ignoring the transform. The size is in viewBox units; multiply it by the display scale and device pixel ratio for a pixel size. It returns null if the key is missing or the first matching sprite has no visible frame with positive dimensions.
Animated dynamic images
Pass animate: true to setImageProvider to play an animated GIF or WebP in a layer, such as an animated avatar:
dynamicEntity.setImageProvider(
'avatar',
provider: NetworkImage(avatarUrl),
circle: true,
animate: true,
);
fit,circle, andcornerRadiusapply to every frame. A single-frame image behaves as it does withoutanimate, and the returned future still completes on the first frame.- Only dynamic images animate. Images embedded in the SVGA file always show their first frame.
- Frames advance only while an
SvgaPlayerusing the entity is showing it: mounted, with tickers enabled, and with animations not disabled for accessibility. This is the rule Flutter'sImagewidget follows for multi-frame images. A player underTickerMode(enabled: false), such as a route covered by another or an offstage tab, or whoseMediaQuerysetsdisableAnimations, does not count. Listening to the entity withaddListenerdoes not count either. While no player is showing the entity, the image keeps its current frame. - An entity shared by several players keeps animating while at least one of them is showing it.
- When a player shows the entity again, the image continues from wherever Flutter's image stream is, without setting it again. If
ImageCacheevicted the image in the meantime, it restarts from its first frame. - No app lifecycle code is needed: while the app is in the background, the engine schedules no frames, so the image stops on its own.
- Timing and looping follow Flutter's
Imagewidget. Frame delays are not clamped, so frames with a zero delay advance on every vsync (flutter/flutter#29130). - If a later frame fails to decode, the layer keeps the last good frame.
Capping dynamic image size
Pass maxImageDimension to setImageProvider to bound the memory of a dynamic image. The value is in pixels, as in SvgaDecodeOptions.maxImageDimension, and there is no cap by default:
dynamicEntity.setImageProvider(
'avatar',
provider: NetworkImage(avatarUrl),
circle: true,
animate: true,
maxImageDimension: 180,
);
- Every frame whose longest side is larger than the cap, the first one and each later animated frame, is downscaled to it before it is painted. The aspect ratio is kept, so
fit,circle, andcornerRadiusbehave as for any other image. - Frames at or under the cap are stored unchanged, without a resize.
- Still images are capped too, so the option means the same thing for every frame count.
- The cap is the only bound on animated frames: Flutter's decoder ignores target sizes such as
ResizeImagefor multi-frame GIF and WebP images (flutter/flutter#137668). - The cap belongs to the call. Setting the key again uses the new call's cap.
Enable audio
Audio playback requires a backend. Add either svga_next_audioplayers or svga_next_just_audio to your app, then register it before loading animations. For example:
import 'package:flutter/widgets.dart';
import 'package:svga_next/svga_next.dart';
import 'package:svga_next_audioplayers/svga_next_audioplayers.dart';
void main() {
SvgaAudio.backend = const SvgaAudioplayersBackend();
runApp(const MyApp());
}
Without a backend, the player ignores audio. To discard audio while decoding, pass decodeOptions: const SvgaDecodeOptions(enableAudio: false) to SvgaPlayer.
Cache and decode options
Loaded movies with a source cache key share an in-memory cache. Set SvgaCache.instance.maxBytes to change its memory budget. To cache downloaded SVGA files on disk, set SvgaConfig.diskCacheDirectory to a writable directory at startup. The disk cache is disabled by default.
SvgaConfig.maxConcurrentLoads limits uncached loads across all sources and cache instances. The default is 2, and the value must be positive. Each slot covers parsing and image decoding. Waiting loads start in FIFO order, while cache hits and requests sharing an in-flight load bypass the queue. Lowering the limit lets active loads finish and applies the new limit to subsequent slot acquisitions.
Use SvgaDecodeOptions(maxImageDimension: 1024) to cap decoded image dimensions, or change decodeConcurrency to limit simultaneous image decodes. Pass the options to SvgaPlayer.decodeOptions or SvgaLoader.load(options: ...). Call SvgaLoader.preload(source) to load a cached animation before showing it.
If you call SvgaLoader.load directly, call release() on the returned SvgaMovie when you no longer need your reference. Assigning it to SvgaController.movie gives the controller its own reference. SvgaPlayer handles this ownership for its own loads.
Limitations
- Web is not supported because loading uses
dart:ioandIsolate.run. - Stroke dashing applies to path shapes, but not rectangles or ellipses.
See the example app for a complete player with dynamic content and audio.