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/8e2d665d697869b18457f4658aa628cc to your computer and use it in GitHub Desktop.

Select an option

Save tejaswini-dev-techie/8e2d665d697869b18457f4658aa628cc to your computer and use it in GitHub Desktop.
A Flutter CustomPainter implementation for a step-based progress indicator. It visually represents progress through a linear progress bar with rounded milestones. As progress advances, the corresponding circles are filled, and checkmarks are drawn for completed steps, excluding the last one. Ideal for use cases like task completion tracking, onb…
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), // Change this dynamically
),
),
),
);
}
}
class StepperCustomPainter extends CustomPainter {
final int filledRounds;
StepperCustomPainter({required this.filledRounds});
@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,
);
// Filled progress bar
Paint filledPaint = Paint()..style = PaintingStyle.fill;
filledPaint.color = const Color(0xff2E2E54);
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(0, size.height * 0.25, size.width * (filledRounds / 4),
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
List<double> circleOffsets = [
size.width * 0.25,
size.width * 0.50,
size.width * 0.75,
size.width * 1.00,
];
for (int i = 0; i < 4; i++) {
bool isFilled = i < filledRounds;
bool hasTickMark = i < filledRounds - 1; // Only previous rounds get a tick mark
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 except the latest filled one
if (hasTickMark) {
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 7 53 45 PM

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