svg_animate 0.5.0 copy "svg_animate: ^0.5.0" to clipboard
svg_animate: ^0.5.0 copied to clipboard

Plays SVGs that declare their own animation, in SMIL or CSS keyframes, on top of the vector_graphics renderer that flutter_svg uses.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:svg_animate/svg_animate.dart';

import 'playback_controls.dart';
import 'playground.dart';

/// One of the animated SVGs shipped with this example.
class Sample {
  /// See class doc.
  const Sample(this.asset, this.title, this.description);

  /// The file under `assets/`.
  final String asset;

  /// What it is.
  final String title;

  /// What it is here to show, which is not always what it looks like: several
  /// of these are ordinary to watch and awkward to compile.
  final String description;
}

/// Ordered so that the ones doing something a still SVG cannot come first.
///
/// `progress.svg` is not in here: it is the one driven by a controller further
/// down the page, and showing it twice would say less rather than more.
const List<Sample> _samples = <Sample>[
  Sample(
    'assets/signature.svg',
    'Drawn on',
    'An animated stroke-dasharray. The dash grows to the length of the path while '
        'the gap after it shrinks away — a list of numbers interpolated as a list.',
  ),
  Sample(
    'assets/reveal.svg',
    'Wiped in',
    'Bars revealed by a clipPath whose rect gets wider and then narrows again. '
        'The clip is resolved for every frame, so the wipe is geometry rather '
        'than an effect.',
  ),
  Sample(
    'assets/gradient.svg',
    'Animated gradient',
    'The gradient itself moves: the stop colours change and so does where the '
        'middle stop sits.',
  ),
  Sample(
    'assets/bars.svg',
    'Started apart',
    'Five bars running the same animation, each held back by its own begin. There '
        'is no shared timeline; they simply agree.',
  ),
  Sample(
    'assets/bounce.svg',
    'Two transforms at once',
    'A fall and a squash on the same element, combined with additive="sum" and '
        'eased by keySplines rather than by a named curve.',
  ),
  Sample(
    'assets/comet.svg',
    'CSS motion path',
    'offset-path with offset-distance animated, and offset-rotate turning the '
        'shape to face the way it is going.',
  ),
  Sample(
    'assets/breathe.svg',
    'Alternating',
    'CSS animation-direction: alternate plays the keyframes forwards and then '
        'backwards, and animation-delay staggers the three circles.',
  ),
  Sample(
    'assets/orbit.svg',
    'SMIL motion path',
    'animateMotion along an mpath, with rotate="auto".',
  ),
  Sample(
    'assets/spinner.svg',
    'Spinner',
    'animateTransform turning an arc, with its stroke colour animated alongside '
        'it on a longer loop.',
  ),
  Sample(
    'assets/pulse.svg',
    'Pulse',
    'CSS @keyframes scaling about transform-origin: center, which is resolved '
        'against the view box.',
  ),
];

void main() {
  runApp(const AnimatedSvgExampleApp());
}

/// An example app that plays the animated SVGs in `assets/`, and lets a visitor
/// try one of their own.
class AnimatedSvgExampleApp extends StatelessWidget {
  /// Creates the example app.
  const AnimatedSvgExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Animated SVG',
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: const Text('Animated SVG'),
            bottom: const TabBar(
              tabs: <Widget>[
                Tab(text: 'Samples'),
                Tab(text: 'Try your own'),
              ],
            ),
          ),
          body: const TabBarView(
            children: <Widget>[_SamplesScreen(), PlaygroundScreen()],
          ),
        ),
      ),
    );
  }
}

/// The animated SVGs that ship with the example.
class _SamplesScreen extends StatelessWidget {
  const _SamplesScreen();

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: <Widget>[
        for (final Sample sample in _samples) _SampleTile(sample: sample),
        const Divider(height: 48),
        const _ControlledSample(),
      ],
    );
  }
}

class _SampleTile extends StatelessWidget {
  const _SampleTile({required this.sample});

  final Sample sample;

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 12),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          // A fixed box rather than the picture's own size: these are 48x48 and
          // 120x48, and a list where every row starts in a different place is
          // harder to read than one that wastes a little space.
          SizedBox(
            width: 104,
            height: 64,
            child: AnimatedSvgPicture.asset(
              sample.asset,
              fit: BoxFit.contain,
              // Half the default. A tile is 64 logical pixels tall, where 30
              // frames a second is not tellable from 60, and this page compiles
              // ten animations before it can show any of them — on the web that
              // happens on the one thread there is.
              frameRate: 30,
              // Without this the row is empty until its animation is compiled,
              // and a page of empty rows reads as broken rather than as busy.
              placeholderBuilder: (BuildContext context) => const _Skeleton(),
            ),
          ),
          const SizedBox(width: 20),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text(sample.title, style: theme.textTheme.titleSmall),
                const SizedBox(height: 4),
                Text(sample.description, style: theme.textTheme.bodySmall),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

/// Stands in for a sample while it is being compiled.
class _Skeleton extends StatelessWidget {
  const _Skeleton();

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 64,
        height: 40,
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.surfaceContainerHighest,
          borderRadius: BorderRadius.circular(8),
        ),
      ),
    );
  }
}

/// Demonstrates driving an animation from outside the widget, so that it can be
/// paused and scrubbed.
class _ControlledSample extends StatefulWidget {
  const _ControlledSample();

  @override
  State<_ControlledSample> createState() => _ControlledSampleState();
}

class _ControlledSampleState extends State<_ControlledSample> {
  final AnimatedSvgController _controller = AnimatedSvgController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 16),
          child: Text('Driven by an AnimatedSvgController'),
        ),
        const SizedBox(height: 16),
        Center(
          child: AnimatedSvgPicture.asset(
            'assets/progress.svg',
            width: 240,
            repeat: false,
            controller: _controller,
          ),
        ),
        PlaybackControls(controller: _controller),
      ],
    );
  }
}
9
likes
0
points
528
downloads

Publisher

verified publishertreamz.me

Weekly Downloads

Plays SVGs that declare their own animation, in SMIL or CSS keyframes, on top of the vector_graphics renderer that flutter_svg uses.

Repository (GitHub)
View/report issues

Topics

#svg #animation #vector-graphics #smil

License

unknown (license)

Dependencies

flutter, flutter_svg, http, path_parsing, vector_graphics, vector_graphics_compiler, xml

More

Packages that depend on svg_animate