如何在Flutter中实现弧形滑块式速度计UI及体重选择弧形控件?
Flutter弧形体重选择控件与速度计UI实现方案
一、弧形体重选择控件实现
核心思路
用CustomPainter绘制弧形选择轨道,通过GestureDetector监听拖动手势计算选中的体重值,同步更新显示文本。
完整代码示例
import 'package:flutter/material.dart'; class ArcWeightPicker extends StatefulWidget { const ArcWeightPicker({super.key}); @override State<ArcWeightPicker> createState() => _ArcWeightPickerState(); } class _ArcWeightPickerState extends State<ArcWeightPicker> { // 体重范围:30kg - 150kg final double minWeight = 30; final double maxWeight = 150; // 当前选中体重 double currentWeight = 60; // 弧形角度范围:从120度到240度(下半圆) final double startAngle = 120 * (3.1415926 / 180); final double sweepAngle = 120 * (3.1415926 / 180); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 显示选中体重 Text( '${currentWeight.toStringAsFixed(0)}kg', style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), // 弧形选择器 SizedBox( width: 300, height: 150, child: GestureDetector( onPanUpdate: (details) { setState(() { // 获取触摸点相对于控件中心的坐标 RenderBox box = context.findRenderObject() as RenderBox; Offset localPos = box.globalToLocal(details.globalPosition); Offset center = Offset(150, 150); // 计算触摸点对应的角度 double angle = (localPos - center).direction - startAngle; // 限制角度在0到sweepAngle之间 angle = angle.clamp(0, sweepAngle); // 转换为体重值 currentWeight = minWeight + (maxWeight - minWeight) * (angle / sweepAngle); }); }, child: CustomPaint( painter: _WeightArcPainter( startAngle: startAngle, sweepAngle: sweepAngle, currentAngle: startAngle + (currentWeight - minWeight)/(maxWeight - minWeight)*sweepAngle, ), ), ), ), ], ); } } class _WeightArcPainter extends CustomPainter { final double startAngle; final double sweepAngle; final double currentAngle; _WeightArcPainter({ required this.startAngle, required this.sweepAngle, required this.currentAngle, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height); final radius = size.width / 2; // 绘制背景弧形轨道 final backgroundPaint = Paint() ..color = Colors.grey[300]! ..style = PaintingStyle.stroke ..strokeWidth = 20; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, sweepAngle, false, backgroundPaint, ); // 绘制选中的弧形进度 final progressPaint = Paint() ..color = Colors.blue ..style = PaintingStyle.stroke ..strokeWidth = 20 ..strokeCap = StrokeCap.round; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, currentAngle - startAngle, false, progressPaint, ); // 绘制选中指针 final pointerPaint = Paint()..color = Colors.blue; canvas.drawCircle( Offset( center.dx + radius * cos(currentAngle), center.dy + radius * sin(currentAngle), ), 10, pointerPaint, ); } @override bool shouldRepaint(covariant _WeightArcPainter oldDelegate) { return currentAngle != oldDelegate.currentAngle; } }
使用方式
直接将ArcWeightPicker作为子控件添加到页面中即可,拖动弧形区域就能实时更新显示的体重值。
二、弧形滑块式速度计UI实现
核心思路
基于CustomPainter绘制刻度弧形轨道、刻度值和指针,通过绑定数值控制指针旋转角度,模拟速度计效果。
完整代码示例
import 'package:flutter/material.dart'; class Speedometer extends StatefulWidget { const Speedometer({super.key}); @override State<Speedometer> createState() => _SpeedometerState(); } class _SpeedometerState extends State<Speedometer> { // 速度范围:0-180km/h double currentSpeed = 60; final double minSpeed = 0; final double maxSpeed = 180; // 弧形角度范围:从150度到30度(左半部分到右半部分的弧形) final double startAngle = 150 * (3.1415926 / 180); final double sweepAngle = 240 * (3.1415926 / 180); @override Widget build(BuildContext context) { return SizedBox( width: 300, height: 200, child: Column( children: [ Text( '${currentSpeed.toStringAsFixed(0)} km/h', style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), Expanded( child: CustomPaint( painter: _SpeedometerPainter( startAngle: startAngle, sweepAngle: sweepAngle, currentSpeed: currentSpeed, minSpeed: minSpeed, maxSpeed: maxSpeed, ), ), ), // 调试用滑块,控制速度值 Slider( min: minSpeed, max: maxSpeed, value: currentSpeed, onChanged: (value) { setState(() { currentSpeed = value; }); }, ), ], ), ); } } class _SpeedometerPainter extends CustomPainter { final double startAngle; final double sweepAngle; final double currentSpeed; final double minSpeed; final double maxSpeed; _SpeedometerPainter({ required this.startAngle, required this.sweepAngle, required this.currentSpeed, required this.minSpeed, required this.maxSpeed, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height); final radius = size.width / 2 - 20; // 绘制背景弧形轨道 final backgroundPaint = Paint() ..color = Colors.grey[300]! ..style = PaintingStyle.stroke ..strokeWidth = 15; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, sweepAngle, false, backgroundPaint, ); // 绘制彩色进度弧形(分三段:绿色、黄色、红色) final greenPaint = Paint()..color = Colors.green; final yellowPaint = Paint()..color = Colors.yellow; final redPaint = Paint()..color = Colors.red; // 每段对应速度范围 double greenSweep = sweepAngle * (60 / maxSpeed); double yellowSweep = sweepAngle * (60 / maxSpeed); double redSweep = sweepAngle * (60 / maxSpeed); canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, greenSweep, false, greenPaint..style = PaintingStyle.stroke..strokeWidth = 15, ); canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle + greenSweep, yellowSweep, false, yellowPaint..style = PaintingStyle.stroke..strokeWidth = 15, ); canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle + greenSweep + yellowSweep, redSweep, false, redPaint..style = PaintingStyle.stroke..strokeWidth = 15, ); // 绘制刻度线和刻度值 final textPainter = TextPainter( textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); final linePaint = Paint()..color = Colors.black..strokeWidth = 2; // 每30km/h一个大刻度,共6个 for (int i = 0; i <= 6; i++) { double angle = startAngle + (sweepAngle / 6) * i; // 大刻度线 canvas.drawLine( Offset( center.dx + radius * cos(angle), center.dy + radius * sin(angle), ), Offset( center.dx + (radius - 15) * cos(angle), center.dy + (radius - 15) * sin(angle), ), linePaint, ); // 刻度值 textPainter.text = TextSpan( text: '${i * 30}', style: const TextStyle(fontSize: 14, color: Colors.black), ); textPainter.layout(); textPainter.paint( canvas, Offset( center.dx + (radius - 35) * cos(angle) - textPainter.width / 2, center.dy + (radius - 35) * sin(angle) - textPainter.height / 2, ), ); } // 计算指针角度 double pointerAngle = startAngle + (currentSpeed / maxSpeed) * sweepAngle; // 绘制指针 final pointerPaint = Paint()..color = Colors.black..strokeWidth = 3..strokeCap = StrokeCap.round; canvas.drawLine( center, Offset( center.dx + (radius - 20) * cos(pointerAngle), center.dy + (radius - 20) * sin(pointerAngle), ), pointerPaint, ); // 指针中心圆点 canvas.drawCircle(center, 8, Paint()..color = Colors.black); } @override bool shouldRepaint(covariant _SpeedometerPainter oldDelegate) { return currentSpeed != oldDelegate.currentSpeed; } }
使用方式
将Speedometer添加到页面中,拖动下方滑块就能看到指针跟随速度值转动,模拟速度计效果。
内容的提问来源于stack exchange,提问作者Sreerekha K V
相关产品推荐
相关产品推荐

