I’m working on a Flutter project where I need to create a stats widget that displays calories required using a multi-valued percentage indicator. I’ve tried several repositories but couldn’t recreate the design. Here’s the Figma UI I’m trying to replicate:
I’ve attempted to use CustomPaint and CustomPainter, but I’m struggling to draw multiple arcs with different colors and values. Here’s my current code:
Could someone guide me on how to achieve this?
Using:
Here's an example.
import 'package:capped_progress_indicator/capped_progress_indicator.dart';
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final FocusNode focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(useMaterial3: false),
home: PieChartWidget(),
);
}
}
class PieChartWidget extends StatelessWidget {
const PieChartWidget({super.key});
@override
Widget build(BuildContext context) {
// Data for the pie chart
Map<String, double> dataMap = {
"Red": 47,
"Green": 41,
"Blue": 16,
};
// List of colors
final colorList = <Color>[
Colors.indigo.shade300,
Colors.red.shade300,
Colors.green.shade300
];
return Scaffold(
body: Center(
child: SizedBox(
height: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
PieChart(
dataMap: dataMap,
colorList: colorList,
chartType: ChartType.ring,
chartRadius: 150,
// centerText: "241",
centerWidget: const Text.rich(TextSpan(children: [
TextSpan(
text: "241\n",
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
TextSpan(text: "kcal")
])),
ringStrokeWidth: 10,
chartValuesOptions: ChartValuesOptions(
showChartValues: false, showChartValueBackground: false),
chartLegendSpacing: 0.0,
legendOptions: LegendOptions(
showLegends: false,
),
// chartValuesOptions: ChartValuesOptions(
// showChartValuesInPercentage: true,
// showChartValuesOutside: false,
// showChartValues: true,
// decimalPlaces: 0,
// ),
// legendOptions: LegendOptions(
// showLegendsInRow: false,
// legendPosition: LegendPosition.right,
// showLegends: true,
// legendShape: BoxShape.circle,
// legendTextStyle: TextStyle(
// fontWeight: FontWeight.bold,
// ),
// ),
),
SizedBox(width: 30),
PercentageWidget(
title: "28.2g",
value: 47,
typeTitle: "carbs",
color: colorList[0],
),
SizedBox(width: 30),
PercentageWidget(
title: "10.9g",
value: 41,
typeTitle: "fat",
color: colorList[1],
),
SizedBox(width: 30),
PercentageWidget(
title: "9.8g",
value: 16,
typeTitle: "protein",
color: colorList[2],
),
],
),
),
),
);
}
}
class PercentageWidget extends StatelessWidget {
const PercentageWidget(
{super.key,
required this.title,
required this.value,
required this.typeTitle,
required this.color});
final String title;
final double value;
final String typeTitle;
final Color color;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 45,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text(
typeTitle,
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w300, color: Colors.grey.shade600),
),
SizedBox(height: 3),
LinearCappedProgressIndicator(
color: color,
backgroundColor: color.withOpacity(0.5),
cornerRadius: 10,
value: value / 100,
minHeight: 6),
SizedBox(height: 2),
Text(
'$value' "%",
style: TextStyle(
color: color,
),
),
],
),
);
}
}
Note: