magic_animations 0.2.0 copy "magic_animations: ^0.2.0" to clipboard
magic_animations: ^0.2.0 copied to clipboard

Beautiful, easy-to-use Flutter animations. Chainable entrance, interactive, and GLSL shader effects with a dead-simple API.

example/lib/main.dart

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

void main() => runApp(const GalleryApp());

class GalleryApp extends StatelessWidget {
  const GalleryApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'magic_animations gallery',
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark(
        useMaterial3: true,
      ).copyWith(scaffoldBackgroundColor: const Color(0xFF11131A)),
      home: const GalleryHome(),
    );
  }
}

class GalleryHome extends StatelessWidget {
  const GalleryHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('magic_animations'), centerTitle: false),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 8, 16, 40),
        children: [
          const _SectionHeader('Entrance'),
          ReplayableDemo(
            title: 'fadeIn()',
            code: "Text('Hello').fadeIn()",
            builder: (c) => const _Chip(
              'Fade',
            ).fadeIn(trigger: AnimateTrigger.manual, controller: c),
          ),
          ReplayableDemo(
            title: 'slideUp()',
            code: 'widget.slideUp()',
            builder: (c) => const _Chip(
              'Slide up',
            ).slideUp(trigger: AnimateTrigger.manual, controller: c),
          ),
          ReplayableDemo(
            title: 'scaleIn()',
            code: 'widget.scaleIn()',
            builder: (c) => const _Chip(
              'Scale',
            ).scaleIn(trigger: AnimateTrigger.manual, controller: c),
          ),
          ReplayableDemo(
            title: 'blurIn()',
            code: 'widget.blurIn()',
            builder: (c) => const _Chip(
              'Blur',
            ).blurIn(trigger: AnimateTrigger.manual, controller: c),
          ),
          ReplayableDemo(
            title: 'flipIn()',
            code: 'widget.flipIn()',
            builder: (c) => const _Chip(
              'Flip',
            ).flipIn(trigger: AnimateTrigger.manual, controller: c),
          ),
          ReplayableDemo(
            title: 'printIn() — unroll through a slot',
            code: 'receipt.printIn(showSlot: true)',
            builder: (c) => const _Receipt().printIn(
              showSlot: true,
              trigger: AnimateTrigger.manual,
              controller: c,
            ),
          ),
          const _StaticDemo(
            title: 'shimmer()',
            code: 'skeleton.shimmer()',
            child: Shimmer(child: _Chip('Loading…', color: Color(0xFFE0E0E0))),
          ),
          ReplayableDemo(
            title: 'CountUp',
            code: 'CountUp(end: 1024, onComplete: ...)',
            builder: (c) => CountUp(
              end: 1024,
              trigger: AnimateTrigger.manual,
              controller: c,
              style: const TextStyle(
                fontSize: 34,
                fontWeight: FontWeight.w800,
                color: Colors.white,
              ),
            ),
          ),
          ReplayableDemo(
            title: 'Typewriter',
            code: "Typewriter(text: '...', cursor: '|')",
            builder: (c) => Typewriter(
              text: 'Welcome to magic_animations!',
              cursor: '|',
              trigger: AnimateTrigger.manual,
              controller: c,
              style: const TextStyle(
                fontSize: 18,
                fontFamily: 'monospace',
                color: Colors.white,
              ),
            ),
          ),
          const _StaggerDemo(),

          const _SectionHeader('Interactive'),
          const _StaticDemo(
            title: 'pressScale() — press me',
            code: 'widget.pressScale(haptics: true)',
            wrap: _Wrap.press,
            child: _Chip('Press me', color: Color(0xFF4C6FFF)),
          ),
          const _StaticDemo(
            title: 'bouncyTap() — tap me',
            code: 'widget.bouncyTap()',
            wrap: _Wrap.bouncy,
            child: _Chip('Tap me', color: Color(0xFFFF6CAB)),
          ),
          const _StaticDemo(
            title: 'rippleTap() — tap me',
            code: 'widget.rippleTap()',
            wrap: _Wrap.ripple,
            child: _Chip('Ripple', color: Color(0xFF23A455)),
          ),
          const _StaticDemo(
            title: 'jiggle()',
            code: 'icon.jiggle()',
            wrap: _Wrap.jiggle,
            child: _Chip('Jiggle', color: Color(0xFFFF9F43)),
          ),

          const _SectionHeader('Shaders (GPU)'),
          const _StaticDemo(
            title: 'glow()',
            code: 'Icon(Icons.star).glow()',
            wrap: _Wrap.glow,
            child: Padding(
              padding: EdgeInsets.all(20),
              child: Icon(Icons.star_rounded, size: 56, color: Colors.white),
            ),
          ),
          const _StaticDemo(
            title: 'gradientFlow()',
            code: "Text('magic_animations').gradientFlow()",
            wrap: _Wrap.gradient,
            child: Text(
              'magic_animations',
              style: TextStyle(fontSize: 34, fontWeight: FontWeight.w800),
            ),
          ),
          const _StaticDemo(
            title: 'liquid()',
            code: 'card.liquid()',
            wrap: _Wrap.liquid,
            child: SizedBox(
              width: 220,
              height: 90,
              child: Center(
                child: Text(
                  'Liquid',
                  style: TextStyle(
                    fontSize: 22,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ),
          ReplayableDemo(
            title: 'dissolve()',
            code: 'widget.dissolve()',
            builder: (c) => const _Chip(
              'Dissolve',
              color: Color(0xFF7C4DFF),
            ).dissolve(trigger: AnimateTrigger.manual, controller: c),
          ),
        ],
      ),
    );
  }
}

/// Chooses how a [_StaticDemo] wraps its child (for the interactive/shader
/// demos that don't use a replay controller).
enum _Wrap { none, press, bouncy, ripple, jiggle, glow, gradient, liquid }

class _SectionHeader extends StatelessWidget {
  const _SectionHeader(this.label);
  final String label;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 28, bottom: 8),
      child: Text(
        label.toUpperCase(),
        style: const TextStyle(
          fontSize: 12,
          letterSpacing: 2,
          fontWeight: FontWeight.w700,
          color: Color(0xFF8A90A6),
        ),
      ),
    );
  }
}

/// A demo card with a Replay button, driven by an [AnimateController].
class ReplayableDemo extends StatefulWidget {
  const ReplayableDemo({
    super.key,
    required this.title,
    required this.code,
    required this.builder,
  });

  final String title;
  final String code;
  final Widget Function(AnimateController controller) builder;

  @override
  State<ReplayableDemo> createState() => _ReplayableDemoState();
}

class _ReplayableDemoState extends State<ReplayableDemo> {
  final AnimateController _controller = AnimateController();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => _controller.play());
  }

  void _replay() {
    _controller.reset();
    _controller.play();
  }

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

  @override
  Widget build(BuildContext context) {
    return _DemoCard(
      title: widget.title,
      code: widget.code,
      trailing: IconButton(
        icon: const Icon(Icons.replay_rounded),
        tooltip: 'Replay',
        onPressed: _replay,
      ),
      child: widget.builder(_controller),
    );
  }
}

/// A demo card whose child animates on its own (loop / on tap / continuous).
class _StaticDemo extends StatelessWidget {
  const _StaticDemo({
    required this.title,
    required this.code,
    required this.child,
    this.wrap = _Wrap.none,
  });

  final String title;
  final String code;
  final Widget child;
  final _Wrap wrap;

  Widget _wrapped() {
    switch (wrap) {
      case _Wrap.none:
        return child;
      case _Wrap.press:
        return child.pressScale(haptics: true);
      case _Wrap.bouncy:
        return child.bouncyTap(haptics: true);
      case _Wrap.ripple:
        return ClipRRect(
          borderRadius: BorderRadius.circular(14),
          child: child.rippleTap(color: const Color(0x55FFFFFF)),
        );
      case _Wrap.jiggle:
        return child.jiggle();
      case _Wrap.glow:
        return child.glow();
      case _Wrap.gradient:
        return child.gradientFlow();
      case _Wrap.liquid:
        return ClipRRect(
          borderRadius: BorderRadius.circular(16),
          child: child.liquid(),
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return _DemoCard(title: title, code: code, child: _wrapped());
  }
}

/// Demos [Stagger] by remounting a fresh `Stagger.list` (with a new [Key])
/// on replay, so each item's `onAppear` cascade plays again from scratch.
class _StaggerDemo extends StatefulWidget {
  const _StaggerDemo();

  @override
  State<_StaggerDemo> createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<_StaggerDemo> {
  int _replayKey = 0;

  @override
  Widget build(BuildContext context) {
    return _DemoCard(
      title: 'Stagger — cascades a list',
      code: "Stagger.list([chipA, chipB, chipC])",
      trailing: IconButton(
        icon: const Icon(Icons.replay_rounded),
        tooltip: 'Replay',
        onPressed: () => setState(() => _replayKey++),
      ),
      child: Row(
        key: ValueKey(_replayKey),
        mainAxisAlignment: MainAxisAlignment.center,
        children:
            Stagger.list([
                  const _Chip('One', color: Color(0xFF4C6FFF)).scaleIn(),
                  const _Chip('Two', color: Color(0xFF23A455)).scaleIn(),
                  const _Chip('Three', color: Color(0xFFFF6CAB)).scaleIn(),
                ], interval: const Duration(milliseconds: 120))
                .map(
                  (w) => Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 5),
                    child: w,
                  ),
                )
                .toList(),
      ),
    );
  }
}

class _DemoCard extends StatelessWidget {
  const _DemoCard({
    required this.title,
    required this.code,
    required this.child,
    this.trailing,
  });

  final String title;
  final String code;
  final Widget child;
  final Widget? trailing;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(bottom: 14),
      decoration: BoxDecoration(
        color: const Color(0xFF1A1D28),
        borderRadius: BorderRadius.circular(18),
        border: Border.all(color: const Color(0xFF262A38)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 12, 8, 0),
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    title,
                    style: const TextStyle(
                      fontWeight: FontWeight.w600,
                      fontSize: 15,
                    ),
                  ),
                ),
                ?trailing,
              ],
            ),
          ),
          Container(
            constraints: const BoxConstraints(minHeight: 150),
            alignment: Alignment.center,
            padding: const EdgeInsets.symmetric(vertical: 12),
            child: child,
          ),
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
            child: Align(
              alignment: Alignment.centerLeft,
              child: Text(
                code,
                style: const TextStyle(
                  fontFamily: 'monospace',
                  fontSize: 12.5,
                  color: Color(0xFF8A90A6),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// A little skeuomorphic receipt used to show off [PrintIn].
class _Receipt extends StatelessWidget {
  const _Receipt();

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
      decoration: const BoxDecoration(
        color: Color(0xFFF7F5EF),
        borderRadius: BorderRadius.vertical(bottom: Radius.circular(6)),
      ),
      child: const DefaultTextStyle(
        style: TextStyle(color: Color(0xFF222222), fontFamily: 'monospace'),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Center(
              child: Text(
                'RUN RECEIPT',
                style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 2),
              ),
            ),
            SizedBox(height: 10),
            _Row('Distance', '5.02 km'),
            _Row('Time', '24:18'),
            _Row('Pace', '4:50 /km'),
            _Row('Calories', '312 kcal'),
            SizedBox(height: 10),
            Center(child: Text('— thank you —')),
          ],
        ),
      ),
    );
  }
}

class _Row extends StatelessWidget {
  const _Row(this.label, this.value);
  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 2),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [Text(label), Text(value)],
      ),
    );
  }
}

/// A simple pill used as demo content.
class _Chip extends StatelessWidget {
  const _Chip(this.label, {this.color = const Color(0xFF2D3142)});
  final String label;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 14),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(14),
      ),
      child: Text(
        label,
        style: const TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.w600,
          color: Colors.white,
        ),
      ),
    );
  }
}
1
likes
160
points
106
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Beautiful, easy-to-use Flutter animations. Chainable entrance, interactive, and GLSL shader effects with a dead-simple API.

Repository (GitHub)
View/report issues

Topics

#animation #ui #effects #shaders #transitions

License

MIT (license)

Dependencies

flutter

More

Packages that depend on magic_animations