Skip to content

Instantly share code, notes, and snippets.

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

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

Select an option

Save tejaswini-dev-techie/e65add1d83cb5c3de8af054cb966fc81 to your computer and use it in GitHub Desktop.
A Flutter CustomPainter implementation for a step-based progress indicator. This widget dynamically renders a progress bar with circles representing steps. Completed steps are filled, and previous steps display a tick mark. The current step is numbered inside the circle. Fully customizable for different step counts and progress states.
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, 50), // Increased height for better visibility
painter: StepperCustomPainter(filledRounds: 3, totalSteps: 7),
),
),
),
);
}
}
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.4, size.width, size.height * 0.2),
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 step width dynamically
double stepWidth = size.width / (totalSteps - 1);
double circleRadius = size.width * 0.035; // Reduced circle size
// Filled progress bar
Paint filledPaint = Paint()..style = PaintingStyle.fill;
filledPaint.color = const Color(0xff2E2E54);
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(0, size.height * 0.4, stepWidth * (filledRounds - 1),
size.height * 0.2),
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
for (int i = 0; i < totalSteps; i++) {
double xPos = stepWidth * i;
bool isFilled = i < filledRounds;
bool hasTickMark = i < filledRounds - 1;
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(xPos, size.height * 0.5),
circleRadius,
// size.width * 0.035, // Increased circle size - radius
fillPaint,
);
canvas.drawCircle(
Offset(xPos, size.height * 0.5),
circleRadius,
// size.width * 0.035, // Increased circle size - radius
borderPaint,
);
// Draw tick mark for previous filled steps
if (hasTickMark) {
Path tickPath = Path();
double tickStartX = xPos - size.width * 0.012;
double tickStartY = size.height * 0.52;
double tickMidX = xPos - size.width * 0.004;
double tickMidY = size.height * 0.58;
double tickEndX = xPos + size.width * 0.012;
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);
}
if (!hasTickMark) {
// Draw step number inside the circle
TextPainter textPainter = TextPainter(
text: TextSpan(
text: (i + 1).toString(),
style: TextStyle(
color: isFilled ? Colors.white : const Color(0xff2E2E54),
fontSize: size.width * 0.03,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
xPos - textPainter.width / 2,
size.height * 0.5 - textPainter.height / 2,
),
);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
@tejaswini-dev-techie
Copy link
Author

Screenshot 2025-02-13 at 8 45 43 PM

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