import 'dart:math';
import 'package:flutter/material.dart';
class CircularTimerWithTaxi extends StatefulWidget {
final int durationInSeconds;
final VoidCallback? onTimerComplete;
final double size;
final Color trackColor;
final Color progressColor;
const CircularTimerWithTaxi({
Key? key,
required this.durationInSeconds,
this.onTimerComplete,
this.size = 300,
this.trackColor = const Color(0xFFE0E0E0),
this.progressColor = const Color(0xFF1A237E),
}) : super(key: key);
@override
State<CircularTimerWithTaxi> createState() => _CircularTimerWithTaxiState();
}
class _CircularTimerWithTaxiState extends State<CircularTimerWithTaxi>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_setupAnimation();
}
void _setupAnimation() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: widget.durationInSeconds),
);
_animation = Tween<double>(
begin: 0.0,
end: 2 * pi,
).animate(_controller);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
widget.onTimerComplete?.call();
}
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Stack(
alignment: Alignment.center,
children: [
// Track and Progress
CustomPaint(
size: Size(widget.size, widget.size),
painter: TrackPainter(
progress: _controller.value,
trackColor: widget.trackColor,
progressColor: widget.progressColor,
),
),
// Moving Car
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..translate(
(widget.size / 2) * cos(_animation.value - pi / 2),
(widget.size / 2) * sin(_animation.value - pi / 2),
)
..rotateZ(_animation.value),
child: const Icon(
Icons.local_taxi,
color: Colors.amber,
size: 30,
),
),
// Timer Text
Text(
'${((1 - _controller.value) * widget.durationInSeconds).ceil()}s',
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
),
),
],
);
},
);
}
}
class TrackPainter extends CustomPainter {
final double progress;
final Color trackColor;
final Color progressColor;
TrackPainter({
required this.progress,
required this.trackColor,
required this.progressColor,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
const strokeWidth = 20.0;
// Draw base track
final trackPaint = Paint()
..color = trackColor
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawCircle(center, radius - (strokeWidth / 2), trackPaint);
// Draw progress
final progressPaint = Paint()
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(
center: center,
radius: radius - (strokeWidth / 2),
),
-pi / 2,
2 * pi * (1 - progress),
false,
progressPaint,
);
// Draw track markers
final markerPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 2;
const markersCount = 40;
for (var i = 0; i < markersCount; i++) {
final angle = (2 * pi * i) / markersCount;
final start = Offset(
center.dx + (radius - strokeWidth) * cos(angle),
center.dy + (radius - strokeWidth) * sin(angle),
);
final end = Offset(
center.dx + radius * cos(angle),
center.dy + radius * sin(angle),
);
canvas.drawLine(start, end, markerPaint);
}
}
@override
bool shouldRepaint(TrackPainter oldDelegate) {
return oldDelegate.progress != progress ||
oldDelegate.trackColor != trackColor ||
oldDelegate.progressColor != progressColor;
}
}
class TimerScreen extends StatelessWidget {
const TimerScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularTimerWithTaxi(
durationInSeconds: 30,
size: 300,
trackColor: Colors.grey[300]!,
progressColor: Colors.blue[900]!,
onTimerComplete: () {
// Handle timer completion
print('Timer finished!');
},
),
),
);
}
}
Smooth Car Animation
Custom Track Design
Timer Functionality
You can customize various aspects of the timer:
CircularTimerWithTaxi(
durationInSeconds: 60, // Duration in seconds
size: 400, // Overall size
trackColor: Colors.grey[200]!, // Background track color
progressColor: Colors.blue[800]!, // Progress track color
onTimerComplete: () {
// Custom completion handling
},
)
// In TrackPainter class
const strokeWidth = 20.0; // Change track thickness
const markersCount = 40; // Change number of dash marks
// Replace the Icon widget with custom widget
Transform(
...
child: Image.asset(
'assets/car_icon.png',
width: 30,
height: 30,
),
)
Text(
'${((1 - _controller.value) * widget.durationInSeconds).ceil()}s',
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.blue[900],
),
)
Car Rotation Issues
Performance Optimization
shouldRepaint
efficientlyAnimation Smoothness
State Management
Responsive Design
Testing
class TimerState extends ChangeNotifier {
bool isRunning = false;
int remainingSeconds = 0;
void startTimer(int duration) {
remainingSeconds = duration;
isRunning = true;
notifyListeners();
}
void stopTimer() {
isRunning = false;
notifyListeners();
}
}
Remember to add necessary dependencies in your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
provider: any # If using provider for state management
This implementation provides a solid foundation for a circular timer with car animation. You can build upon this base to add more features or customize it further based on your specific needs.
Would you like me to explain any part in more detail or add specific customizations?