flutter_phase_animator 0.0.1 copy "flutter_phase_animator: ^0.0.1" to clipboard
flutter_phase_animator: ^0.0.1 copied to clipboard

A Flutter animation package inspired by SwiftUI's PhaseAnimator. Chain steps sequentially or loop through phases declaratively

example/lib/main.dart

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

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Phase Animator Demo',
      theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
      home: const _PhaseAnimatorScreen(),
    );
  }
}

class _PhaseAnimatorScreen extends StatefulWidget {
  const _PhaseAnimatorScreen();

  @override
  State<_PhaseAnimatorScreen> createState() => _PhaseAnimatorScreenState();
}

class _PhaseAnimatorScreenState extends State<_PhaseAnimatorScreen> {
  final _sequencerKey = GlobalKey<PhaseAnimatorState>();
  bool _trigger = false;
  bool _loopPaused = false;

  void _replayChain() {
    setState(() => _trigger = false);
    Future.microtask(() {
      setState(() => _trigger = true);
      _sequencerKey.currentState?.replay();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D0D0D),
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        title: const Text('PhaseAnimator Demo'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // ── Section 1: Chained ──────────────────────
            _sectionLabel('PhaseAnimator  (chained)'),
            const SizedBox(height: 16),
            Center(
              child: PhaseAnimator(
                key: _sequencerKey,
                autoPlay: true,
                steps: [
                  // Step 1: fade in
                  AnimationStep(
                    duration: const Duration(milliseconds: 500),
                    curve: Curves.easeIn,
                    builder: (ctx, t, child) =>
                        Opacity(opacity: t, child: child),
                  ),
                  // Step 2: slide up
                  AnimationStep(
                    duration: const Duration(milliseconds: 400),
                    curve: Curves.easeOut,
                    builder: (ctx, t, child) => Transform.translate(
                      offset: Offset(0, (1 - t) * 40),
                      child: child,
                    ),
                  ),
                  // Step 3: scale bounce
                  AnimationStep(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.elasticOut,
                    builder: (ctx, t, child) =>
                        Transform.scale(scale: 0.8 + t * 0.2, child: child),
                  ),
                ],
                child: Container(
                  width: 180,
                  height: 180,
                  decoration: BoxDecoration(
                    gradient: const LinearGradient(
                      colors: [Color(0xFF6C63FF), Color(0xFFFF6584)],
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                    ),
                    borderRadius: BorderRadius.circular(24),
                    boxShadow: [
                      BoxShadow(
                        color: const Color(0xFF6C63FF).withOpacity(0.5),
                        blurRadius: 32,
                        offset: const Offset(0, 8),
                      ),
                    ],
                  ),
                  child: const Center(
                    child: Text('🚀', style: TextStyle(fontSize: 56)),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 16),
            Center(
              child: ElevatedButton.icon(
                onPressed: _replayChain,
                icon: const Icon(Icons.replay),
                label: const Text('Replay Sequence'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: const Color(0xFF6C63FF),
                  foregroundColor: Colors.white,
                ),
              ),
            ),

            const SizedBox(height: 48),

            //── Section 2: Looping ──────────────────────
            _sectionLabel('PhaseAnimatorLooping  (phase-based)'),
            const SizedBox(height: 16),
            Center(
              child: PhaseAnimatorLooping(
                paused: _loopPaused,
                phaseDuration: const Duration(milliseconds: 700),
                curve: Curves.easeInOut,
                phases: const [
                  Phase(opacity: 1.0, scale: 1.0, offset: Offset(0, 0)),
                  Phase(opacity: 0.6, scale: 1.3, offset: Offset(0, -20)),
                  Phase(opacity: 1.0, scale: 0.9, offset: Offset(0, 10)),
                  Phase(opacity: 0.8, scale: 1.1, offset: Offset(0, -5)),

                  Phase(opacity: 1.0, scale: 0.6, offset: Offset(0, 10)),
                  Phase(opacity: 0.3, scale: 1.5, offset: Offset(0, -5)),
                ],
                builder: (ctx, phase, child) => Opacity(
                  opacity: phase.opacity,
                  child: Transform.translate(
                    offset: phase.offset,
                    child: Transform.scale(scale: phase.scale, child: child),
                  ),
                ),
                child: Container(
                  width: 140,
                  height: 140,
                  decoration: BoxDecoration(
                    color: const Color(0xFFFF6584),
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color: const Color(0xFFFF6584).withOpacity(0.5),
                        blurRadius: 40,
                        spreadRadius: 4,
                      ),
                    ],
                  ),
                  child: const Center(
                    child: Text('✨', style: TextStyle(fontSize: 48)),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 16),
            Center(
              child: ElevatedButton.icon(
                onPressed: () => setState(() => _loopPaused = !_loopPaused),
                icon: Icon(_loopPaused ? Icons.play_arrow : Icons.pause),
                label: Text(_loopPaused ? 'Resume' : 'Pause'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: const Color(0xFFFF6584),
                  foregroundColor: Colors.white,
                ),
              ),
            ),

            const SizedBox(height: 48),

            // ── Section 3: Combined ─────────────────────
            _sectionLabel('Combined — enter sequence + looping idle'),
            const SizedBox(height: 16),
            const _CombinedDemo(),

            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }

  Widget _sectionLabel(String text) => Text(
    text,
    style: const TextStyle(
      color: Colors.white70,
      fontSize: 13,
      fontWeight: FontWeight.w600,
      letterSpacing: 0.5,
    ),
  );
}

/// Shows how to chain PhaseAnimator into PhaseAnimatorLooping:
/// entrance sequence plays once, then the looping idle starts.
class _CombinedDemo extends StatefulWidget {
  const _CombinedDemo();

  @override
  State<_CombinedDemo> createState() => _CombinedDemoState();
}

class _CombinedDemoState extends State<_CombinedDemo> {
  bool _entranceDone = false;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: PhaseAnimator(
        autoPlay: true,
        onComplete: () => setState(() => _entranceDone = true),
        steps: [
          AnimationStep(
            duration: const Duration(milliseconds: 600),
            curve: Curves.easeOut,
            builder: (ctx, t, child) => Opacity(opacity: t, child: child),
          ),
          AnimationStep(
            duration: const Duration(milliseconds: 2500),
            curve: Curves.elasticOut,
            builder: (ctx, t, child) => Transform.scale(scale: t, child: child),
          ),
        ],
        child: PhaseAnimatorLooping(
          paused: !_entranceDone,
          phaseDuration: const Duration(milliseconds: 800),
          phases: const [
            Phase(scale: 1.0, rotation: 0.0),
            Phase(scale: 1.05, rotation: 0.05),
            Phase(scale: 1.0, rotation: -0.05),
            Phase(scale: 0.97, rotation: 0.0),
          ],
          builder: (ctx, phase, child) => Transform(
            alignment: Alignment.center,
            transform: Matrix4.identity()
              ..scale(phase.scale)
              ..rotateZ(phase.rotation),
            child: child,
          ),
          child: Container(
            width: 160,
            height: 160,
            decoration: BoxDecoration(
              gradient: const LinearGradient(
                colors: [Color(0xFF43E97B), Color(0xFF38F9D7)],
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
              ),
              borderRadius: BorderRadius.circular(32),
              boxShadow: [
                BoxShadow(
                  color: const Color(0xFF43E97B).withOpacity(0.4),
                  blurRadius: 40,
                  spreadRadius: 4,
                ),
              ],
            ),
            child: const Center(
              child: Text('🎯', style: TextStyle(fontSize: 52)),
            ),
          ),
        ),
      ),
    );
  }
}
3
likes
160
points
24
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter animation package inspired by SwiftUI's PhaseAnimator. Chain steps sequentially or loop through phases declaratively

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_phase_animator