Skip to content

Instantly share code, notes, and snippets.

@tejaswini-dev-techie
Last active February 13, 2025 15:27
Show Gist options
  • Select an option

  • Save tejaswini-dev-techie/3e8991d4d09394f3f9a55f0db451d350 to your computer and use it in GitHub Desktop.

Select an option

Save tejaswini-dev-techie/3e8991d4d09394f3f9a55f0db451d350 to your computer and use it in GitHub Desktop.
A custom progress bar with a linear fill and dynamic step positioning.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: CustomPaint(
size: const Size(328, 12),
painter:
StepperCustomPainter(filledRounds: 3, totalSteps: 5), // Dynamic steps
),
),
),
);
}
}
class StepperCustomPainter extends CustomPainter {
final int filledRounds;
final int totalSteps;
StepperCustomPainter({required this.filledRounds, required this.totalSteps});
@override
void paint(Canvas canvas, Size size) {
// Background progress bar
Paint backgroundPaint = Paint()..style = PaintingStyle.fill;
backgroundPaint.color = const Color(0xffE9E5EF);
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(0, size.height * 0.25, size.width, size.height * 0.5),
bottomRight: Radius.circular(size.width * 0.009),
bottomLeft: Radius.circular(size.width * 0.009),
topLeft: Radius.circular(size.width * 0.009),
topRight: Radius.circular(size.width * 0.009),
),
backgroundPaint,
);
// Calculate the width of each step
double stepWidth = size.width / (totalSteps - 1); // Corrected to (totalSteps - 1)
// Filled progress bar: Limit the width to the number of filled rounds
Paint filledPaint = Paint()..style = PaintingStyle.fill;
filledPaint.color = const Color(0xff2E2E54);
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(0, size.height * 0.25, stepWidth * (filledRounds - 1), // Start from first step
size.height * 0.5),
bottomRight: Radius.circular(size.width * 0.009),
bottomLeft: Radius.circular(size.width * 0.009),
topLeft: Radius.circular(size.width * 0.009),
topRight: Radius.circular(size.width * 0.009),
),
filledPaint,
);
// Circle positions dynamically based on totalSteps
List<double> circleOffsets = [];
for (int i = 0; i < totalSteps; i++) {
circleOffsets.add(stepWidth * i); // Corrected to start from the very beginning
}
for (int i = 0; i < totalSteps; i++) {
bool isFilled = i < filledRounds;
Paint borderPaint = Paint()
..color = isFilled ? Colors.white : const Color(0xff2E2E54)
..style = PaintingStyle.stroke
..strokeWidth = size.width * 0.003;
Paint fillPaint = Paint()..style = PaintingStyle.fill;
fillPaint.color =
isFilled ? const Color(0xff2E2E54) : const Color(0xffE9E5EF);
// Draw circle with border
canvas.drawCircle(
Offset(circleOffsets[i], size.height * 0.5),
size.width * 0.0175,
fillPaint,
);
canvas.drawCircle(
Offset(circleOffsets[i], size.height * 0.5),
size.width * 0.0175,
borderPaint,
);
// Draw tick mark for filled circles
if (isFilled) {
Path tickPath = Path();
double tickStartX = circleOffsets[i] - size.width * 0.006;
double tickStartY = size.height * 0.52;
double tickMidX = circleOffsets[i] - size.width * 0.002;
double tickMidY = size.height * 0.58;
double tickEndX = circleOffsets[i] + size.width * 0.006;
double tickEndY = size.height * 0.42;
tickPath.moveTo(tickStartX, tickStartY);
tickPath.lineTo(tickMidX, tickMidY);
tickPath.lineTo(tickEndX, tickEndY);
Paint tickPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = size.width * 0.003
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
canvas.drawPath(tickPath, tickPaint);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
@tejaswini-dev-techie
Copy link
Author

Screenshot 2025-02-13 at 8 09 02 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment