Skip to main content

animation_next 1.0.0 copy "animation_next: ^1.0.0" to clipboard
animation_next: ^1.0.0 copied to clipboard

Presets for fade, slide, zoom, flip, and bounce, plus a timed animation composer.

example/lib/main.dart

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

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

/// One spec, one player.
class AnimationNextExample extends StatelessWidget {
  /// Creates the example.
  const AnimationNextExample({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'animation_next',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const MotionStory(),
    );
  }
}

class MotionStory extends StatefulWidget {
  const MotionStory({super.key});

  @override
  State<MotionStory> createState() => _MotionStoryState();
}

class _MotionStoryState extends State<MotionStory> {
  int _play = 0;
  bool _reduced = false;
  bool _warm = false;

  @override
  Widget build(BuildContext context) {
    final media = MediaQuery.of(context);
    return MediaQuery(
      data: media.copyWith(disableAnimations: _reduced),
      child: Scaffold(
        appBar: AppBar(title: const Text('One motion, then play it')),
        body: ListView(
          padding: const EdgeInsets.fromLTRB(24, 24, 24, 48),
          children: [
            Text(
              'NextMotion is the change. NextMotionView plays it. '
              'The curve and the delay are on that timeline. '
              'Reduced motion jumps to the end.',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 12,
              runSpacing: 12,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: [
                FilledButton(
                  onPressed: () => setState(() => _play++),
                  child: const Text('Play again'),
                ),
                FilterChip(
                  label: const Text('Reduced motion'),
                  selected: _reduced,
                  onSelected: (value) => setState(() => _reduced = value),
                ),
              ],
            ),
            const SizedBox(height: 24),
            const Text('Same fade. Different curve and delay.'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 12,
              runSpacing: 12,
              children: [
                _Tile(
                  key: ValueKey('linear-$_play'),
                  label: 'linear',
                  child: const _Box(label: 'linear').fadeIn(
                    variant: NextFadeIn.fadeInUp,
                    curve: Curves.linear,
                    duration: const Duration(milliseconds: 900),
                  ),
                ),
                _Tile(
                  key: ValueKey('ease-$_play'),
                  label: 'easeIn',
                  child: const _Box(label: 'easeIn').fadeIn(
                    variant: NextFadeIn.fadeInUp,
                    curve: Curves.easeIn,
                    duration: const Duration(milliseconds: 900),
                  ),
                ),
                _Tile(
                  key: ValueKey('wait-$_play'),
                  label: 'easeOut, after 500ms',
                  child: const _Box(label: 'wait').fadeIn(
                    variant: NextFadeIn.fadeInUp,
                    delay: const Duration(milliseconds: 500),
                    duration: const Duration(milliseconds: 900),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 28),
            const Text(
              'Scroll sideways. Each tile plays once when it appears.',
            ),
            const SizedBox(height: 8),
            SizedBox(
              height: 72,
              child: ListView(
                scrollDirection: Axis.horizontal,
                children: [
                  for (var i = 0; i < 8; i++)
                    Padding(
                      padding: const EdgeInsets.only(right: 12),
                      child: _Box(label: 'Tile ${i + 1}').fadeIn(
                        variant: NextFadeIn.fadeInUp,
                        playWhenVisible: true,
                      ),
                    ),
                ],
              ),
            ),
            const SizedBox(height: 28),
            const Text(
              'Compose a list. A spring plays unless you pass a curve.',
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 12,
              runSpacing: 16,
              children: [
                _kind('fade', const [
                  NextFade(to: 1, initial: 0, duration: _span),
                ]),
                _kind('move', const [
                  NextMove(y: 0, initialY: 28, duration: _span),
                ]),
                _kind('scale', const [
                  NextScale(
                    by: 1,
                    initial: 0.4,
                    spring: NextSpring.bouncy,
                    duration: _span,
                  ),
                ]),
                _kind('rotate', const [
                  NextRotate(degrees: 0, initial: -16, duration: _span),
                ]),
                _kind('skew', const [
                  NextSkew(x: 0, initialX: 14, duration: _span),
                ]),
                _kind('size', const [
                  NextSize(
                    width: 140,
                    initialWidth: 48,
                    height: 64,
                    initialHeight: 64,
                    duration: _span,
                  ),
                ]),
                _kind('blur', const [
                  NextBlur(to: 0, initial: 6, duration: _span),
                ]),
                _kind('tint', const [
                  NextTint(
                    to: Color(0x00000000),
                    initial: Color(0x99E65100),
                    duration: _span,
                  ),
                ]),
                _kind('radius', const [
                  NextRadius(to: 28, initial: 0, duration: _span),
                ]),
                _kind('reveal', const [NextReveal(duration: _span)]),
                _kind('shadow', const [
                  NextShadow(
                    to: BoxShadow(
                      color: Color(0x66000000),
                      blurRadius: 16,
                      offset: Offset(0, 8),
                    ),
                    duration: _span,
                  ),
                ]),
              ],
            ),
            const SizedBox(height: 28),
            const Text('Same move. Four springs.'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 12,
              runSpacing: 16,
              children: [
                for (final spring in const [
                  (NextSpring.smooth, 'smooth'),
                  (NextSpring.gentle, 'gentle'),
                  (NextSpring.snappy, 'snappy'),
                  (NextSpring.bouncy, 'bouncy'),
                ])
                  _Tile(
                    key: ValueKey('${spring.$2}-$_play'),
                    label: spring.$2,
                    child: _Box(label: spring.$2).animate([
                      NextMove(
                        y: 0,
                        initialY: 24,
                        spring: spring.$1,
                        duration: const Duration(milliseconds: 900),
                      ),
                    ]),
                  ),
              ],
            ),
            const SizedBox(height: 28),
            const Text('Chained, looping, and reversing.'),
            const SizedBox(height: 8),
            const _Box(label: 'chain').animate(
              const [
                NextFade(to: 1, initial: 0),
                NextMove(y: 0, initialY: 24),
                NextScale(by: 1, initial: 0.9),
                NextBlur(to: 0, initial: 6),
                NextMove(x: 28, start: Duration(milliseconds: 600)),
                NextFade(
                  to: 0.4,
                  start: Duration(milliseconds: 1200),
                  duration: Duration(milliseconds: 400),
                ),
              ],
              repeat: NextAnimated.forever,
              reverse: true,
              alignment: Alignment.bottomLeft,
              key: ValueKey('chain-$_play'),
            ),
            const SizedBox(height: 28),
            const Text('NextColorTween replays when the target color changes.'),
            const SizedBox(height: 8),
            NextColorTween(
              begin: Colors.indigo,
              end: _warm ? Colors.deepOrange : Colors.indigo,
              duration: const Duration(milliseconds: 500),
              builder: (context, color) {
                return Align(
                  alignment: Alignment.centerLeft,
                  child: Material(
                    color: color,
                    borderRadius: BorderRadius.circular(12),
                    child: InkWell(
                      onTap: () => setState(() => _warm = !_warm),
                      borderRadius: BorderRadius.circular(12),
                      child: const SizedBox(
                        width: 160,
                        height: 72,
                        child: Center(
                          child: Text(
                            'Tap',
                            style: TextStyle(color: Colors.white),
                          ),
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  Widget _kind(String label, List<NextAnimation> animations) {
    return _Tile(
      key: ValueKey('$label-$_play'),
      label: label,
      child: _Box(label: label).animate(animations),
    );
  }
}

const _span = Duration(milliseconds: 700);

class _Tile extends StatelessWidget {
  const _Tile({required this.label, required this.child, super.key});

  final String label;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 180,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(label, style: Theme.of(context).textTheme.labelLarge),
          const SizedBox(height: 8),
          child,
        ],
      ),
    );
  }
}

class _Box extends StatelessWidget {
  const _Box({required this.label});

  final String label;

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return Container(
      width: 160,
      height: 64,
      alignment: Alignment.center,
      color: scheme.primaryContainer,
      child: Text(label, style: TextStyle(color: scheme.onPrimaryContainer)),
    );
  }
}
3
likes
160
points
699
downloads

Documentation

API reference

Publisher

verified publishershashiben.me

Weekly Downloads

Presets for fade, slide, zoom, flip, and bounce, plus a timed animation composer.

Homepage
Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

flutter, visibility_detector

More

Packages that depend on animation_next