Forked from tejaswini-dev-techie/animated_count_down_timer.dart
Created
July 13, 2025 00:51
-
-
Save rodrigo-sys/8de48f81edba264da923f711423cc44e to your computer and use it in GitHub Desktop.
Building a Countdown Timer in Flutter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:async'; | |
| import 'package:flutter/material.dart'; | |
| void main() { | |
| runApp(const MyApp()); | |
| } | |
| class MyApp extends StatelessWidget { | |
| const MyApp({Key? key}) : super(key: key); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| title: 'Flutter Demo', | |
| theme: ThemeData( | |
| primarySwatch: Colors.deepPurple, | |
| ), | |
| home: const TimerScreen(), | |
| ); | |
| } | |
| } | |
| class TimerScreen extends StatefulWidget { | |
| const TimerScreen({Key? key}) : super(key: key); | |
| @override | |
| _TimerScreenState createState() => _TimerScreenState(); | |
| } | |
| class _TimerScreenState extends State<TimerScreen> { | |
| static const Duration countdownDuration = Duration(minutes: 0, seconds: 10); | |
| final ValueNotifier<Duration> durationNotifier = | |
| ValueNotifier<Duration>(countdownDuration); | |
| Timer? timer; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| startTimer(); | |
| } | |
| @override | |
| void dispose() { | |
| timer?.cancel(); | |
| durationNotifier.dispose(); | |
| super.dispose(); | |
| } | |
| void startTimer() { | |
| timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime()); | |
| } | |
| void addTime() { | |
| final seconds = durationNotifier.value.inSeconds - 1; | |
| if (seconds < 0) { | |
| timer?.cancel(); | |
| // Handle end of timer here | |
| showEndMessage(); | |
| } else { | |
| durationNotifier.value = Duration(seconds: seconds); | |
| } | |
| } | |
| void showEndMessage() { | |
| showDialog( | |
| context: context, | |
| builder: (BuildContext context) { | |
| return AlertDialog( | |
| title: const Text("Timer Ended"), | |
| content: const Text("The timer has ended."), | |
| actions: <Widget>[ | |
| TextButton( | |
| child: const Text("OK"), | |
| onPressed: () { | |
| Navigator.of(context).pop(); | |
| }, | |
| ), | |
| ], | |
| ); | |
| }, | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return WillPopScope( | |
| onWillPop: _onWillPop, | |
| child: Scaffold( | |
| backgroundColor: const Color(0xFFadd8e6), | |
| body: Center( | |
| child: ValueListenableBuilder<Duration>( | |
| valueListenable: durationNotifier, | |
| builder: (context, duration, child) { | |
| return Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| buildTime(duration), | |
| if (duration.inSeconds <= 0) // Show "END" when timer is zero or less | |
| const Padding( | |
| padding: EdgeInsets.only(top: 20.0), | |
| child: Text( | |
| "END", | |
| style: TextStyle( | |
| color: Colors.white, | |
| fontSize: 36, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ); | |
| }, | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| Future<bool> _onWillPop() async { | |
| timer?.cancel(); | |
| Navigator.of(context).pop(); | |
| return true; | |
| } | |
| Widget buildTime(Duration duration) { | |
| String twoDigits(int n) => n.toString().padLeft(2, '0'); | |
| final hours = twoDigits(duration.inHours); | |
| final minutes = twoDigits(duration.inMinutes.remainder(60)); | |
| final seconds = twoDigits(duration.inSeconds.remainder(60)); | |
| return Row( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| buildTimeColumn(hours, "Hrs"), | |
| buildTimeColumn(minutes, "Mins"), | |
| buildTimeColumn(seconds, "Secs", isLast: true), | |
| ], | |
| ); | |
| } | |
| Widget buildTimeColumn(String time, String label, {bool isLast = false}) { | |
| return Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| Row( | |
| children: [ | |
| buildDigit(time[0]), | |
| buildDigit(time[1]), | |
| if (!isLast) buildTimeSeparator(), | |
| ], | |
| ), | |
| buildLabel(label), | |
| ], | |
| ); | |
| } | |
| Widget buildDigit(String digit) { | |
| return Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), | |
| margin: const EdgeInsets.symmetric(horizontal: 2), | |
| decoration: BoxDecoration( | |
| color: Colors.white, | |
| borderRadius: BorderRadius.circular(8), | |
| ), | |
| child: ClipRect( | |
| child: AnimatedSwitcher( | |
| duration: const Duration(milliseconds: 600), | |
| switchInCurve: Curves.easeOutExpo, | |
| switchOutCurve: Curves.easeInExpo, | |
| transitionBuilder: (Widget child, Animation<double> animation) { | |
| return Stack( | |
| children: <Widget>[ | |
| SlideTransition( | |
| position: Tween<Offset>( | |
| begin: const Offset(0, -1), | |
| end: const Offset(0, 1), | |
| ).animate(CurvedAnimation( | |
| parent: animation, | |
| curve: Curves.easeOutCubic, | |
| )), | |
| child: FadeTransition( | |
| opacity: animation, | |
| child: child, | |
| ), | |
| ), | |
| SlideTransition( | |
| position: Tween<Offset>( | |
| begin: const Offset(0, -1), | |
| end: const Offset(0, 0), | |
| ).animate(CurvedAnimation( | |
| parent: animation, | |
| curve: Curves.bounceIn, | |
| )), | |
| child: FadeTransition( | |
| opacity: animation, | |
| child: child, | |
| ), | |
| ), | |
| ], | |
| ); | |
| }, | |
| child: Text( | |
| digit, | |
| key: ValueKey<String>(digit), | |
| style: const TextStyle( | |
| fontWeight: FontWeight.bold, | |
| color: Colors.black, | |
| fontSize: 50, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget buildLabel(String label) { | |
| return Text( | |
| label, | |
| style: const TextStyle( | |
| color: Colors.white, | |
| fontSize: 20, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ); | |
| } | |
| Widget buildTimeSeparator() { | |
| return const Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 2.0), | |
| child: Text( | |
| ":", | |
| style: TextStyle( | |
| color: Colors.white, | |
| fontSize: 50, | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment