test.dart 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. import 'package:fl_chart/fl_chart.dart';
  4. void main() {
  5. runApp(const MaterialApp(
  6. home: Scaffold(
  7. backgroundColor: Colors.white,
  8. body: Center(child: MySolidPieChart()),
  9. ),
  10. ));
  11. }
  12. class MySolidPieChart extends StatefulWidget {
  13. const MySolidPieChart({super.key});
  14. @override
  15. State<MySolidPieChart> createState() => _MySolidPieChartState();
  16. }
  17. class _MySolidPieChartState extends State<MySolidPieChart> {
  18. int? touchedIndex;
  19. final double baseRadius = 60.0;
  20. final List<_PieData> data = [
  21. _PieData(label: "运行", value: 45, color: Colors.teal),
  22. _PieData(label: "移动", value: 14, color: Colors.blue),
  23. _PieData(label: "停留", value: 25, color: Colors.green),
  24. _PieData(label: "其他", value: 16, color: Colors.orange),
  25. ];
  26. @override
  27. Widget build(BuildContext context) {
  28. return SizedBox(
  29. width: 320,
  30. height: 320,
  31. child: Stack(
  32. children: [
  33. PieChart(
  34. PieChartData(
  35. sectionsSpace: 0,
  36. centerSpaceRadius: 0,
  37. // ✅ 实心饼图
  38. startDegreeOffset: -90,
  39. pieTouchData: PieTouchData(
  40. touchCallback: (event, response) {
  41. setState(() {
  42. final index = response?.touchedSection?.touchedSectionIndex;
  43. touchedIndex = (index != null && index >= 0) ? index : null;
  44. });
  45. },
  46. ),
  47. sections: List.generate(data.length, (i) {
  48. final item = data[i];
  49. final isTouched = i == touchedIndex;
  50. return PieChartSectionData(
  51. color: item.color,
  52. value: item.value.toDouble(),
  53. title: '${item.value}%',
  54. radius: isTouched ? baseRadius + 10 : baseRadius,
  55. titleStyle: const TextStyle(
  56. fontSize: 14,
  57. fontWeight: FontWeight.bold,
  58. color: Colors.white,
  59. ),
  60. titlePositionPercentageOffset: 0.6,
  61. );
  62. }),
  63. ),
  64. ),
  65. IgnorePointer(
  66. child: CustomPaint(
  67. size: const Size(320, 320),
  68. painter: PieLineLabelPainter(
  69. center: const Offset(160, 160),
  70. baseRadius: baseRadius + 10,
  71. startAngleOffset: -90,
  72. data: data,
  73. touchedIndex: touchedIndex,
  74. ),
  75. ),
  76. ),
  77. ],
  78. ),
  79. );
  80. }
  81. }
  82. class PieLineLabelPainter extends CustomPainter {
  83. final Offset center;
  84. final double baseRadius;
  85. final double startAngleOffset;
  86. final List<_PieData> data;
  87. final int? touchedIndex;
  88. PieLineLabelPainter({
  89. required this.center,
  90. required this.baseRadius,
  91. required this.startAngleOffset,
  92. required this.data,
  93. required this.touchedIndex,
  94. });
  95. @override
  96. void paint(Canvas canvas, Size size) {
  97. if (touchedIndex == null ||
  98. touchedIndex! < 0 ||
  99. touchedIndex! >= data.length) return;
  100. final paint = Paint()
  101. ..color = data[touchedIndex!].color
  102. ..strokeWidth = 1.5;
  103. final total = data.fold(0, (sum, e) => sum + e.value);
  104. double angle = startAngleOffset * pi / 180;
  105. for (int i = 0; i < data.length; i++) {
  106. final sweepAngle = (data[i].value / total) * 2 * pi;
  107. if (i == touchedIndex) {
  108. final midAngle = angle + sweepAngle / 2;
  109. final startR = baseRadius + 2;
  110. final bendR = startR + 25;
  111. final start = Offset(
  112. center.dx + cos(midAngle) * startR,
  113. center.dy + sin(midAngle) * startR,
  114. );
  115. final bend = Offset(
  116. center.dx + cos(midAngle) * bendR,
  117. center.dy + sin(midAngle) * bendR,
  118. );
  119. final isRight = cos(midAngle) >= 0;
  120. final end = Offset(
  121. bend.dx + (isRight ? 30 : -30),
  122. bend.dy,
  123. );
  124. canvas.drawCircle(start, 3, paint);
  125. canvas.drawLine(start, bend, paint);
  126. canvas.drawLine(bend, end, paint);
  127. final textPainter = TextPainter(
  128. text: TextSpan(
  129. text: '${data[i].label} 11h30min',
  130. style: TextStyle(color: data[i].color, fontSize: 12),
  131. ),
  132. textDirection: TextDirection.ltr,
  133. )..layout();
  134. textPainter.paint(
  135. canvas,
  136. Offset(
  137. isRight ? end.dx + 4 : end.dx - textPainter.width - 4,
  138. end.dy - 6,
  139. ),
  140. );
  141. }
  142. angle += sweepAngle;
  143. }
  144. }
  145. @override
  146. bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
  147. }
  148. class _PieData {
  149. final String label;
  150. final int value;
  151. final Color color;
  152. _PieData({required this.label, required this.value, required this.color});
  153. }