smil_animated_svg 0.2.0
smil_animated_svg: ^0.2.0 copied to clipboard
Render SMIL-animated SVGs in Flutter with programmatic pause / resume / speed control and per-colour replacement.
Summary #
Render SMIL-animated SVGs in Flutter with programmatic pause / resume / speed / colour control — no native plugins, no platform channels, no external rasteriser.
(GitHub and pub.dev both stream the SVGs above with their SMIL animations intact — so the strip above plays live, no GIF/MP4 needed.)
Two widgets ship in the box:
| Widget | What it does |
|---|---|
SvgFrame |
Leaf renderer. Draws one frame of an SVG at a given position (0..1). No ticker, no animation controller. Drop one in for a static SVG, or wrap one in an AnimatedBuilder to drive position yourself. |
AnimatedSvg |
Self-playing. Owns an internal AnimationController and plays the SVG's natural cycle (or a custom duration). Adds curve, repeat, stopAt, position (freeze), and tween shortcuts for color, transform, and opacity. |
Both take a colorMap that swaps the SVG's own colours as it renders (see Colour replacement), and there are built-in AnimatedSvgFilters for whole-image effects: grayscale, sepia, invert, tint, colorize.
Supported SVG subset: <g>, <path>, <rect>, <circle>, <ellipse>, <line>, <polygon>, <polyline>, <animate>, <animateTransform> (translate, rotate, scale, skewX, skewY, matrix). calcMode="linear|spline|discrete" with keyTimes and keySplines. From/to/by shorthands. Indefinite and finite repeatCount. No gradients, no <use>, no <text>, no <image>, no <animateMotion>, no CSS filter effects.
Installation #
flutter pub add smil_animated_svg
Usage #
import 'package:smil_animated_svg/smil_animated_svg.dart';
import 'package:flutter/material.dart';
class Loader extends StatelessWidget {
const Loader({super.key});
@override
Widget build(BuildContext context) {
return AnimatedSvg.asset(
'assets/spinner.svg',
width: 64,
height: 64,
// Override the SVG's intrinsic cycle.
duration: const Duration(seconds: 1),
// Tint to match the active theme without re-authoring the SVG.
color: Theme.of(context).colorScheme.primary,
colorBlendMode: BlendMode.srcIn,
);
}
}
Other source constructors #
SvgFrame.string(svgXml, position: 0.4);
SvgFrame.network('https://example.com/icon.svg');
AnimatedSvg.parsed(parseSvg(svgXml)); // pre-parsed, zero load cost
Frame-stepping a static SvgFrame #
AnimatedBuilder(
animation: myController,
builder: (context, _) => SvgFrame.asset(
'assets/pulse.svg',
position: myController.value,
),
);
Tweens that ride the playback timeline #
AnimatedSvg.asset(
'assets/icon.svg',
colorTween: ColorTween(begin: Colors.red, end: Colors.blue),
transformTween: Matrix4Tween(
begin: Matrix4.identity(),
end: Matrix4.diagonal3Values(1.2, 1.2, 1.0),
),
);
Tween values are evaluated at the curved controller value during playback, or at the frozen position when one is supplied.
Error and placeholder builders #
SvgFrame.network(
'https://example.com/icon.svg',
placeholderBuilder: (context) => const CircularProgressIndicator(),
errorBuilder: (context, error, stack) => const Icon(Icons.broken_image),
);
The signatures match Image.errorBuilder / Image.frameBuilder conventions. With no errorBuilder, a failed load collapses to an empty SizedBox instead of throwing — bad SVGs never take a frame down.
Colour replacement #
An SVG is just markup, so its colours can be swapped before it is drawn. Hand either widget a colorMap and one asset serves any number of palettes — no second file, no duplicated artwork to keep in sync:
// Authored in the mint palette; rendered in the love palette.
final loveSwap = {
const Color(0xFF48C374): const Color(0xFFFBA3C8),
const Color(0xFF17924D): const Color(0xFFE1619C),
};
SvgFrame.asset('assets/menu_lists.svg', colorMap: loveSwap);
- Keys match the parsed colour, not the text in the file.
white,#fff,#ffffffandrgb(255,255,255)are all one key, so the author's spelling never has to match the caller's. - Alpha is ignored when matching, and the two alphas multiply. A
#17924D80fill still matches an opaque key, and mapping to a half-transparent replacement fades the result. A swap can never make a translucent part of the artwork solid. - Anything that isn't a colour is left exactly as authored —
none,inherit,currentColor, aurl(#gradient)paint server, and any colour the map has no entry for. - Animated colours interpolate in the new palette. The swap is applied to the parsed tree, keyframes included, so a
fillthe SVG animates from green to white becomes pink to white all the way through — not just at the instant it sits on a keyframe. Per-frame painting cost is unchanged. - One recoloured tree per palette is cached, so a list of forty rows in one palette pays for the swap once.
- Unrelated to
color/colorFilter, which composite over the finished render —colorwith the defaultBlendMode.srcInflattens the whole SVG to a single-colour silhouette, so the two are rarely useful together.
Note that Color has no primitive equality, so a colorMap literal cannot be const. Hold the palette in a top-level final and the widget rebuild cost stays nil.
Parse cache #
Repeated mounts of the same assetPath or url reuse a parsed SvgRoot (64-entry LRU, hot-reload-aware). A recoloured tree is cached next to the one it came from, under a key that includes the palette. Call SvgFrame.clearCache() to drop everything — useful in tests.
🔍 For more information, refer to the API reference.
💬 Contributing and Discussions #
This is an open-source project, and we warmly welcome contributions from everyone, regardless of experience level. Whether you're a seasoned developer or just starting out, contributing to this project is a fantastic way to learn, share your knowledge, and make a meaningful impact on the community.
☝️ Ways you can contribute #
- Find us on Discord: Feel free to ask questions and engage with the community here: https://discord.gg/gEQ8y2nfyX.
- Share your ideas: Every perspective matters, and your ideas can spark innovation.
- Help others: Engage with other users by offering advice, solutions, or troubleshooting assistance.
- Report bugs: Help us identify and fix issues to make the project more robust.
- Suggest improvements or new features: Your ideas can help shape the future of the project.
- Help clarify documentation: Good documentation is key to accessibility. You can make it easier for others to get started by improving or expanding our documentation.
- Write articles: Share your knowledge by writing tutorials, guides, or blog posts about your experiences with the project. It's a great way to contribute and help others learn.
No matter how you choose to contribute, your involvement is greatly appreciated and valued!
☕ We drink a lot of coffee... #
If you're enjoying this package and find it valuable, consider showing your appreciation with a small donation. Every bit helps in supporting future development. You can donate here: https://www.buymeacoffee.com/dev_cetera
LICENSE #
This project is released under the MIT License. See LICENSE for more information.