animated_date_header 1.0.1 copy "animated_date_header: ^1.0.1" to clipboard
animated_date_header: ^1.0.1 copied to clipboard

A Flutter widget that animates day number and month name transitions with blur, slide, scale, and bounce effects. Just pass a DateTime — the widget handles per-letter staggered animations automatically.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Animated Date Header — Examples',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const ExampleHome(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Animated Date Header')),
      body: ListView(
        children: [
          _tile(
            context,
            title: 'Basic Usage',
            subtitle: 'Minimal setup — just pass a date',
            page: const BasicExample(),
          ),
          _tile(
            context,
            title: 'Custom Styles',
            subtitle: 'Large colorful header with custom fonts',
            page: const StyledExample(),
          ),
          _tile(
            context,
            title: 'Full App Bar',
            subtitle: 'Date header with navigation & action buttons',
            page: const AppBarExample(),
          ),
          _tile(
            context,
            title: 'Standalone AnimatedDigit',
            subtitle: 'Use AnimatedDigit on its own for any text',
            page: const DigitExample(),
          ),
        ],
      ),
    );
  }

  Widget _tile(
    BuildContext context, {
    required String title,
    required String subtitle,
    required Widget page,
  }) {
    return ListTile(
      title: Text(title),
      subtitle: Text(subtitle),
      trailing: const Icon(Icons.chevron_right),
      onTap: () => Navigator.push(
        context,
        MaterialPageRoute(builder: (_) => page),
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// Example 1: Basic Usage
// ---------------------------------------------------------------------------
class BasicExample extends StatefulWidget {
  const BasicExample({super.key});

  @override
  State<BasicExample> createState() => _BasicExampleState();
}

class _BasicExampleState extends State<BasicExample> {
  DateTime _date = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Basic Usage')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedDateHeader(date: _date),
            const SizedBox(height: 40),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                FilledButton.tonal(
                  onPressed: () => setState(() {
                    _date = _date.subtract(const Duration(days: 1));
                  }),
                  child: const Text('Previous Day'),
                ),
                const SizedBox(width: 12),
                FilledButton.tonal(
                  onPressed: () => setState(() {
                    _date = _date.add(const Duration(days: 1));
                  }),
                  child: const Text('Next Day'),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                FilledButton.tonal(
                  onPressed: () => setState(() {
                    _date = DateTime(_date.year, _date.month - 1, _date.day);
                  }),
                  child: const Text('Previous Month'),
                ),
                const SizedBox(width: 12),
                FilledButton.tonal(
                  onPressed: () => setState(() {
                    _date = DateTime(_date.year, _date.month + 1, _date.day);
                  }),
                  child: const Text('Next Month'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// Example 2: Custom Styles
// ---------------------------------------------------------------------------
class StyledExample extends StatefulWidget {
  const StyledExample({super.key});

  @override
  State<StyledExample> createState() => _StyledExampleState();
}

class _StyledExampleState extends State<StyledExample> {
  DateTime _date = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Custom Styles')),
      backgroundColor: const Color(0xFF1A1A2E),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedDateHeader(
              date: _date,
              style: const AnimatedDateHeaderStyle(
                dayStyle: TextStyle(
                  fontSize: 48,
                  fontWeight: FontWeight.w900,
                  color: Color(0xFFE94560),
                  height: 1,
                ),
                monthStyle: TextStyle(
                  fontSize: 48,
                  fontWeight: FontWeight.w900,
                  color: Color(0xFF0F3460),
                  height: 1,
                ),
                dayDigitHeight: 54,
                monthLetterHeight: 54,
                dayMonthSpacing: 10,
                monthLetterStagger: Duration(milliseconds: 50),
              ),
            ),
            const SizedBox(height: 40),
            AnimatedDateHeader(
              date: _date,
              style: const AnimatedDateHeaderStyle(
                dayStyle: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.w300,
                  color: Colors.white70,
                  height: 1,
                  letterSpacing: 2,
                ),
                monthStyle: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.w300,
                  color: Colors.white38,
                  height: 1,
                  letterSpacing: 2,
                ),
                dayDigitHeight: 24,
                monthLetterHeight: 24,
                dayMonthSpacing: 8,
              ),
            ),
            const SizedBox(height: 48),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton.filled(
                  onPressed: () => setState(() {
                    _date = _date.subtract(const Duration(days: 1));
                  }),
                  icon: const Icon(Icons.remove),
                ),
                const SizedBox(width: 20),
                IconButton.filled(
                  onPressed: () => setState(() {
                    _date = _date.add(const Duration(days: 1));
                  }),
                  icon: const Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// Example 3: Full App Bar with date list
// ---------------------------------------------------------------------------
class AppBarExample extends StatefulWidget {
  const AppBarExample({super.key});

  @override
  State<AppBarExample> createState() => _AppBarExampleState();
}

class _AppBarExampleState extends State<AppBarExample> {
  late DateTime _selectedDate;
  late List<DateTime> _allDates;
  late ScrollController _scrollController;

  static const _dayLetters = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
  static const bgGrey = Color(0xFFF8F8F8);
  static const cardGrey = Color(0xFFEDEDED);
  static const textDark = Color(0xFF333333);

  @override
  void initState() {
    super.initState();
    _selectedDate = DateTime.now();
    _scrollController = ScrollController();
    _buildDateRange();
    WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToSelected());
  }

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

  void _buildDateRange() {
    final today = DateTime.now();
    final start = DateTime(today.year, today.month - 2, 1);
    final end = DateTime(today.year, today.month + 3, 0);
    _allDates = [];
    for (var d = start; !d.isAfter(end); d = d.add(const Duration(days: 1))) {
      _allDates.add(DateTime(d.year, d.month, d.day));
    }
  }

  void _scrollToSelected({bool animate = false}) {
    if (!_scrollController.hasClients) return;
    final idx = _allDates.indexWhere((d) => _isSameDay(d, _selectedDate));
    if (idx == -1) return;
    const itemWidth = 50.0;
    final viewWidth = _scrollController.position.viewportDimension;
    final target = (idx * itemWidth - viewWidth / 2 + itemWidth / 2 + 16)
        .clamp(0.0, _scrollController.position.maxScrollExtent);
    if (animate) {
      _scrollController.animateTo(target,
          duration: const Duration(milliseconds: 350), curve: Curves.easeOut);
    } else {
      _scrollController.jumpTo(target);
    }
  }

  bool _isSameDay(DateTime a, DateTime b) =>
      a.year == b.year && a.month == b.month && a.day == b.day;

  void _selectDate(DateTime date) {
    if (_isSameDay(date, _selectedDate)) return;
    setState(() => _selectedDate = date);
    WidgetsBinding.instance
        .addPostFrameCallback((_) => _scrollToSelected(animate: true));
  }

  void _goToPreviousMonth() {
    final prev = DateTime(_selectedDate.year, _selectedDate.month - 1, 1);
    final maxDay = DateTime(prev.year, prev.month + 1, 0).day;
    _selectDate(
        DateTime(prev.year, prev.month, _selectedDate.day.clamp(1, maxDay)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: bgGrey,
      body: SafeArea(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.fromLTRB(12, 8, 12, 16),
              child: Row(
                children: [
                  Material(
                    color: cardGrey,
                    shape: const CircleBorder(),
                    clipBehavior: Clip.antiAlias,
                    child: InkWell(
                      onTap: _goToPreviousMonth,
                      customBorder: const CircleBorder(),
                      child: const SizedBox(
                        width: 40,
                        height: 40,
                        child: Icon(Icons.chevron_left_rounded,
                            color: textDark, size: 26),
                      ),
                    ),
                  ),
                  Expanded(
                    child: Padding(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 8, vertical: 4),
                      child: AnimatedDateHeader(
                        date: _selectedDate,
                        style: const AnimatedDateHeaderStyle(
                          dayStyle: TextStyle(
                            fontSize: 26,
                            fontWeight: FontWeight.bold,
                            color: textDark,
                            height: 1,
                          ),
                          monthStyle: TextStyle(
                            fontSize: 26,
                            fontWeight: FontWeight.bold,
                            color: textDark,
                            height: 1,
                          ),
                          dayDigitHeight: 30,
                          monthLetterHeight: 30,
                        ),
                      ),
                    ),
                  ),
                  Container(
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(14),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.black.withValues(alpha: 0.06),
                          blurRadius: 16,
                          offset: const Offset(0, 4),
                        ),
                      ],
                    ),
                    child: Material(
                      color: Colors.transparent,
                      child: InkWell(
                        onTap: () {},
                        borderRadius: BorderRadius.circular(14),
                        child: const SizedBox(
                          width: 44,
                          height: 40,
                          child: Icon(Icons.more_horiz_rounded,
                              color: textDark, size: 24),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 8),
            SizedBox(
              height: 50,
              child: ListView.builder(
                controller: _scrollController,
                scrollDirection: Axis.horizontal,
                padding: const EdgeInsets.symmetric(horizontal: 16),
                itemCount: _allDates.length,
                itemExtent: 50,
                itemBuilder: (context, index) {
                  final date = _allDates[index];
                  final isSelected = _isSameDay(date, _selectedDate);
                  final isCurrentMonth =
                      date.month == _selectedDate.month &&
                          date.year == _selectedDate.year;
                  final letter = _dayLetters[date.weekday - 1];

                  return GestureDetector(
                    onTap: () => _selectDate(date),
                    child: AnimatedContainer(
                      duration: const Duration(milliseconds: 250),
                      width: 44,
                      height: 60,
                      margin: const EdgeInsets.symmetric(horizontal: 3),
                      decoration: BoxDecoration(
                        color: isSelected ? textDark : cardGrey,
                        borderRadius: BorderRadius.circular(14),
                      ),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          AnimatedDefaultTextStyle(
                            duration: const Duration(milliseconds: 250),
                            style: TextStyle(
                              fontSize: 11,
                              fontWeight: FontWeight.w500,
                              color: isSelected
                                  ? Colors.white.withValues(alpha: 0.7)
                                  : isCurrentMonth
                                      ? textDark.withValues(alpha: 0.4)
                                      : textDark.withValues(alpha: 0.2),
                            ),
                            child: Text(letter),
                          ),
                          const SizedBox(height: 2),
                          AnimatedDefaultTextStyle(
                            duration: const Duration(milliseconds: 250),
                            style: TextStyle(
                              fontSize: 18,
                              fontWeight: isSelected
                                  ? FontWeight.w600
                                  : FontWeight.w400,
                              color: isSelected
                                  ? Colors.white
                                  : isCurrentMonth
                                      ? textDark.withValues(alpha: 0.55)
                                      : textDark.withValues(alpha: 0.25),
                            ),
                            child: Text('${date.day}'),
                          ),
                        ],
                      ),
                    ),
                  );
                },
              ),
            ),
            const Spacer(),
            const Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                'Back button, action buttons, and date list are YOUR UI.\n'
                'Only the "15 April" header is from the package.',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.black38, fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// Example 4: Standalone AnimatedDigit
// ---------------------------------------------------------------------------
class DigitExample extends StatefulWidget {
  const DigitExample({super.key});

  @override
  State<DigitExample> createState() => _DigitExampleState();
}

class _DigitExampleState extends State<DigitExample> {
  final GlobalKey<AnimatedDigitState> _digitKey =
      GlobalKey<AnimatedDigitState>();

  int _counter = 0;

  void _increment() {
    _counter = (_counter + 1) % 10;
    _digitKey.currentState?.animateTo('$_counter', Duration.zero);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Standalone AnimatedDigit')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text(
              'AnimatedDigit works for any character:',
              style: TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 24),
            AnimatedDigit(
              key: _digitKey,
              digit: '0',
              textStyle: const TextStyle(
                fontSize: 80,
                fontWeight: FontWeight.w900,
                color: Colors.deepPurple,
                height: 1,
              ),
              containerHeight: 90,
            ),
            const SizedBox(height: 32),
            FilledButton.icon(
              onPressed: _increment,
              icon: const Icon(Icons.add),
              label: const Text('Next Digit'),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
150
points
143
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter widget that animates day number and month name transitions with blur, slide, scale, and bounce effects. Just pass a DateTime — the widget handles per-letter staggered animations automatically.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on animated_date_header