| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- import 'dart:math';
- import 'package:flutter/material.dart';
- import 'package:fl_chart/fl_chart.dart';
- void main() {
- runApp(const MaterialApp(
- home: Scaffold(
- backgroundColor: Colors.white,
- body: Center(child: MySolidPieChart()),
- ),
- ));
- }
- class MySolidPieChart extends StatefulWidget {
- const MySolidPieChart({super.key});
- @override
- State<MySolidPieChart> createState() => _MySolidPieChartState();
- }
- class _MySolidPieChartState extends State<MySolidPieChart> {
- int? touchedIndex;
- final double baseRadius = 60.0;
- final List<_PieData> data = [
- _PieData(label: "运行", value: 45, color: Colors.teal),
- _PieData(label: "移动", value: 14, color: Colors.blue),
- _PieData(label: "停留", value: 25, color: Colors.green),
- _PieData(label: "其他", value: 16, color: Colors.orange),
- ];
- @override
- Widget build(BuildContext context) {
- return SizedBox(
- width: 320,
- height: 320,
- child: Stack(
- children: [
- PieChart(
- PieChartData(
- sectionsSpace: 0,
- centerSpaceRadius: 0,
- // ✅ 实心饼图
- startDegreeOffset: -90,
- pieTouchData: PieTouchData(
- touchCallback: (event, response) {
- setState(() {
- final index = response?.touchedSection?.touchedSectionIndex;
- touchedIndex = (index != null && index >= 0) ? index : null;
- });
- },
- ),
- sections: List.generate(data.length, (i) {
- final item = data[i];
- final isTouched = i == touchedIndex;
- return PieChartSectionData(
- color: item.color,
- value: item.value.toDouble(),
- title: '${item.value}%',
- radius: isTouched ? baseRadius + 10 : baseRadius,
- titleStyle: const TextStyle(
- fontSize: 14,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- titlePositionPercentageOffset: 0.6,
- );
- }),
- ),
- ),
- IgnorePointer(
- child: CustomPaint(
- size: const Size(320, 320),
- painter: PieLineLabelPainter(
- center: const Offset(160, 160),
- baseRadius: baseRadius + 10,
- startAngleOffset: -90,
- data: data,
- touchedIndex: touchedIndex,
- ),
- ),
- ),
- ],
- ),
- );
- }
- }
- class PieLineLabelPainter extends CustomPainter {
- final Offset center;
- final double baseRadius;
- final double startAngleOffset;
- final List<_PieData> data;
- final int? touchedIndex;
- PieLineLabelPainter({
- required this.center,
- required this.baseRadius,
- required this.startAngleOffset,
- required this.data,
- required this.touchedIndex,
- });
- @override
- void paint(Canvas canvas, Size size) {
- if (touchedIndex == null ||
- touchedIndex! < 0 ||
- touchedIndex! >= data.length) return;
- final paint = Paint()
- ..color = data[touchedIndex!].color
- ..strokeWidth = 1.5;
- final total = data.fold(0, (sum, e) => sum + e.value);
- double angle = startAngleOffset * pi / 180;
- for (int i = 0; i < data.length; i++) {
- final sweepAngle = (data[i].value / total) * 2 * pi;
- if (i == touchedIndex) {
- final midAngle = angle + sweepAngle / 2;
- final startR = baseRadius + 2;
- final bendR = startR + 25;
- final start = Offset(
- center.dx + cos(midAngle) * startR,
- center.dy + sin(midAngle) * startR,
- );
- final bend = Offset(
- center.dx + cos(midAngle) * bendR,
- center.dy + sin(midAngle) * bendR,
- );
- final isRight = cos(midAngle) >= 0;
- final end = Offset(
- bend.dx + (isRight ? 30 : -30),
- bend.dy,
- );
- canvas.drawCircle(start, 3, paint);
- canvas.drawLine(start, bend, paint);
- canvas.drawLine(bend, end, paint);
- final textPainter = TextPainter(
- text: TextSpan(
- text: '${data[i].label} 11h30min',
- style: TextStyle(color: data[i].color, fontSize: 12),
- ),
- textDirection: TextDirection.ltr,
- )..layout();
- textPainter.paint(
- canvas,
- Offset(
- isRight ? end.dx + 4 : end.dx - textPainter.width - 4,
- end.dy - 6,
- ),
- );
- }
- angle += sweepAngle;
- }
- }
- @override
- bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
- }
- class _PieData {
- final String label;
- final int value;
- final Color color;
- _PieData({required this.label, required this.value, required this.color});
- }
|