So, I want to show a timer. Below is a simple design of what I want to achieve. The car should go around the track as the time gradually decreases. I currently have no idea how to implement it. There is also the thing where the face of the car should rotate as it goes around the loop. If someone has done something similar in the past, please do let me know. Any guides/tutorials would be greatly appreciated.
Add package: path_drawing
And try to run this code. For now, I used a timer to update the position of the car you can do it based on your requirement.
The car image is added in asset folder. Car Image used in this example.
Center image is also added in asset folder ("pluto-done.png") and need to convert it because it'll also draw using custom painter.
import 'dart:async';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_drawing/path_drawing.dart' as pd;
class CustomRoadWithCar extends StatefulWidget {
const CustomRoadWithCar({super.key});
@override
State<CustomRoadWithCar> createState() => _CustomRoadWithCarState();
}
class _CustomRoadWithCarState extends State<CustomRoadWithCar> {
Timer? _timer;
int _elapsedSeconds = 0;
// Variable to control the movement of the car and path fill
double _carProgress = 0.0;
ui.Image? carImage;
ui.Image? centerImage;
@override
void initState() {
super.initState();
// Load the car image from the asset
loadImageFromAsset('assets/car.png', isCarImage: true);
// Load the center image from the asset
loadImageFromAsset('assets/pluto-done.png', isCarImage: false);
startTimer();
}
void startTimer() {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
_elapsedSeconds++;
// Calculate the progress based on elapsed seconds
_carProgress = _elapsedSeconds / 59.0;
// Check if the elapsed time has reached 59 seconds
if (_elapsedSeconds >= 59) {
// Stop the timer
_timer?.cancel();
}
});
});
}
@override
void dispose() {
// Cancel the timer when the widget is disposed
_timer?.cancel();
super.dispose();
}
// Load an image from assets and convert it to a ui.Image object
Future<void> loadImageFromAsset(String path,
{required bool isCarImage}) async {
// Load the asset data
final ByteData data = await rootBundle.load(path);
final Uint8List bytes = data.buffer.asUint8List();
// Decode the image data
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
final ui.FrameInfo frameInfo = await codec.getNextFrame();
final ui.Image loadedImage = frameInfo.image;
// Set the loaded image to the appropriate state variable
setState(() {
if (isCarImage) {
// If the image is for the car, set it to the image variable
carImage = loadedImage;
} else {
// If the image is for the center of the circle, set it to the centerImage variable
centerImage = loadedImage;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Circle Border Drawing'),
),
body: Center(
child: Stack(
children: [
//grey color road
CustomPaint(
size: const Size(
200, 200), // Set the size of the CustomPaint widget
painter: RoadPainter(25.0), // Set the border width
),
//to be filled by blue color
CustomPaint(
size: const Size(
200, 200), // Set the size of the CustomPaint widget
painter: CircleBorderPainter(
carProgress:
_carProgress, // Pass the current progress to the painter
borderWidth: 25.0,
image: carImage,
centerImage: centerImage,
fillColor: const Color(0xff243347)),
),
Text("$_elapsedSeconds")
],
),
),
);
}
}
class CircleBorderPainter extends CustomPainter {
final double carProgress; // Progress of the car and path fill (0.0 to 1.0)
final double borderWidth;
final ui.Image? image;
final ui.Image? centerImage;
final Color fillColor;
CircleBorderPainter(
{required this.carProgress,
required this.borderWidth,
required this.image,
required this.fillColor,
required this.centerImage});
@override
void paint(Canvas canvas, Size size) {
final double radius = size.width / 2;
final Paint paint = Paint()
..color = fillColor
..style = PaintingStyle.stroke
..strokeWidth = borderWidth
..strokeCap = StrokeCap.round;
// Calculate the arc angle based on progress
double sweepAngle = carProgress * 2 * pi;
final center = size.center(Offset.zero);
// Draw the arc up to the current progress
Rect rect = Rect.fromCircle(center: center, radius: radius);
canvas.drawArc(rect, -pi / 2, sweepAngle, false, paint);
// Calculate the car's position along the arc based on progress
double carAngle = -pi / 2 + sweepAngle;
double carX = size.width / 2 + radius * cos(carAngle);
double carY = size.height / 2 + radius * sin(carAngle);
Offset carPosition = Offset(carX, carY);
DashedCirclePainter dashedCirclePainter = DashedCirclePainter(
strokeWidth: 1.0,
color: Colors.white,
dashPattern: [10.0, 5.0], // Dash length and gap length
);
dashedCirclePainter.paint(canvas, size, center, radius);
// Draw the car image at the calculated position
if (image != null) {
// Desired image width set to 24
double desiredImageWidth = 24;
// Calculate the image aspect ratio
double imageAspectRatio =
image!.width.toDouble() / image!.height.toDouble();
// Calculate the height based on the desired width and aspect ratio
double desiredImageHeight = desiredImageWidth / imageAspectRatio;
// Save the canvas state
canvas.save();
// Translate the canvas to the car position
canvas.translate(carPosition.dx, carPosition.dy);
// Rotate the canvas based on the car's angle
canvas.rotate(carAngle);
// Draw the car image at the car position
canvas.drawImageRect(
image!,
Rect.fromLTWH(0, 0, image!.width.toDouble(), image!.height.toDouble()),
Rect.fromCenter(
center: Offset.zero,
width: desiredImageWidth,
height: desiredImageHeight),
Paint(),
);
// Restore the canvas state
canvas.restore();
}
// Draw the image at the center of the circle
if (centerImage != null) {
// Desired image width set to 50
double desiredImageWidth = 50;
// Calculate the image aspect ratio
double centerImageAspectRatio =
centerImage!.width.toDouble() / centerImage!.height.toDouble();
// Calculate the height based on the desired width and aspect ratio
double desiredImageHeight = desiredImageWidth / centerImageAspectRatio;
// Calculate the rectangle where the image should be drawn
Rect imageRect = Rect.fromCenter(
center: center,
width: desiredImageWidth,
height: desiredImageHeight,
);
// Draw the image at the center of the circle
canvas.drawImageRect(
centerImage!,
Rect.fromLTWH(0, 0, centerImage!.width.toDouble(),
centerImage!.height.toDouble()),
imageRect,
Paint(),
);
}
}
@override
bool shouldRepaint(CircleBorderPainter oldDelegate) {
return oldDelegate.carProgress != carProgress ||
oldDelegate.borderWidth != borderWidth ||
oldDelegate.image != image;
}
}
class RoadPainter extends CustomPainter {
final double borderWidth;
RoadPainter(this.borderWidth);
@override
void paint(Canvas canvas, Size size) {
// Paint for the base road
final Paint roadPaint = Paint()
..color = Colors.grey // Grey color for the road
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
// Calculate the radius and center of the canvas
final double radius = size.width / 2;
final Offset center = size.center(Offset.zero);
// Draw the base circle (road) with specified border width
canvas.drawCircle(center, radius, roadPaint);
DashedCirclePainter dashedCirclePainter = DashedCirclePainter(
strokeWidth: 1.0,
color: Colors.white,
dashPattern: [10.0, 5.0], // Dash length and gap length
);
dashedCirclePainter.paint(canvas, size, center, radius);
}
@override
bool shouldRepaint(RoadPainter oldDelegate) {
return oldDelegate.borderWidth != borderWidth;
}
}
class DashedCirclePainter {
final double strokeWidth;
final Color color;
final List<double> dashPattern;
DashedCirclePainter({
required this.strokeWidth,
required this.color,
required this.dashPattern,
});
void paint(Canvas canvas, Size size, Offset center, double radius) {
// Paint for the red dashed circle
final Paint dashedCirclePaint = Paint()
..color = color // Color for the dashed circle
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth;
// Create a Path for the red dashed circle
Path dashedCirclePath = Path();
dashedCirclePath.addOval(Rect.fromCircle(center: center, radius: radius));
// Draw the dashed red circle using dashPath function
canvas.drawPath(
pd.dashPath(
dashedCirclePath,
dashArray: pd.CircularIntervalList<double>(dashPattern),
),
dashedCirclePaint,
);
}
}
Hope It helps...