flutter如何实现自定义尺子功能
在Flutter中如何实现一个自定义的尺子功能?需要支持滑动测量、刻度自定义以及数值实时显示。目前尝试过使用ListView和CustomPaint,但滑动流畅度和精度不够理想。想请教是否有更好的实现方案或推荐的第三方库?最好能提供核心代码示例或实现思路。
2 回复
在Flutter中实现自定义尺子功能,可以通过CustomPainter绘制刻度线和数字。以下是核心步骤:
-
创建CustomPainter子类,在paint方法中使用Canvas绘制:
- 用drawLine画刻度线(主/副刻度)
- 用drawParagraph画数字标签
-
刻度计算:
double interval = 10; // 刻度间隔 for(double i=minValue; i<=maxValue; i+=interval){ double position = (i - minValue) * pixelPerUnit; // 绘制刻度线 } -
添加手势交互:
GestureDetector( onPanUpdate: (details) { setState(() { // 更新选中位置 }); } ) -
可封装为StatefulWidget,支持自定义:
- 刻度范围
- 刻度密度
- 颜色样式
- 回调选中值
可通过Stack组合其他控件实现游标效果,使用Transform实现横向/竖向切换。
更多关于flutter如何实现自定义尺子功能的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中实现自定义尺子功能可以通过CustomPainter绘制,以下是核心实现步骤:
1. 创建尺子绘制类
class RulerPainter extends CustomPainter {
final double startValue;
final double endValue;
final double currentValue;
final bool isHorizontal;
RulerPainter({
required this.startValue,
required this.endValue,
required this.currentValue,
this.isHorizontal = true,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.grey
..strokeWidth = 1.0;
// 绘制主尺子线
if (isHorizontal) {
canvas.drawLine(Offset(0, size.height), Offset(size.width, size.height), paint);
} else {
canvas.drawLine(Offset(0, 0), Offset(0, size.height), paint);
}
// 绘制刻度
final totalUnits = endValue - startValue;
final pixelsPerUnit = isHorizontal ? size.width / totalUnits : size.height / totalUnits;
for (double i = startValue; i <= endValue; i++) {
final position = (i - startValue) * pixelsPerUnit;
// 主刻度(每10个单位)
if (i % 10 == 0) {
final lineLength = isHorizontal ? 20.0 : 20.0;
if (isHorizontal) {
canvas.drawLine(
Offset(position, size.height),
Offset(position, size.height - lineLength),
paint,
);
// 绘制数值
_drawText(canvas, i.toStringAsFixed(0), Offset(position, size.height - 25));
} else {
canvas.drawLine(
Offset(0, position),
Offset(lineLength, position),
paint,
);
_drawText(canvas, i.toStringAsFixed(0), Offset(25, position));
}
}
// 次刻度(每1个单位)
else if (i % 1 == 0) {
final lineLength = isHorizontal ? 10.0 : 10.0;
if (isHorizontal) {
canvas.drawLine(
Offset(position, size.height),
Offset(position, size.height - lineLength),
paint,
);
} else {
canvas.drawLine(
Offset(0, position),
Offset(lineLength, position),
paint,
);
}
}
}
// 绘制当前值指示器
final indicatorPaint = Paint()
..color = Colors.red
..strokeWidth = 2.0;
final currentPosition = (currentValue - startValue) * pixelsPerUnit;
if (isHorizontal) {
canvas.drawLine(
Offset(currentPosition, 0),
Offset(currentPosition, size.height),
indicatorPaint,
);
} else {
canvas.drawLine(
Offset(0, currentPosition),
Offset(size.width, currentPosition),
indicatorPaint,
);
}
}
void _drawText(Canvas canvas, String text, Offset offset) {
final textPainter = TextPainter(
text: TextSpan(
text: text,
style: const TextStyle(color: Colors.black, fontSize: 12),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(canvas, offset);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
2. 创建可交互的尺子组件
class CustomRuler extends StatefulWidget {
final double startValue;
final double endValue;
final bool isHorizontal;
final ValueChanged<double>? onValueChanged;
const CustomRuler({
super.key,
this.startValue = 0,
this.endValue = 100,
this.isHorizontal = true,
this.onValueChanged,
});
@override
State<CustomRuler> createState() => _CustomRulerState();
}
class _CustomRulerState extends State<CustomRuler> {
double _currentValue = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
setState(() {
if (widget.isHorizontal) {
_currentValue = _calculateValue(details.localPosition.dx);
} else {
_currentValue = _calculateValue(details.localPosition.dy);
}
});
widget.onValueChanged?.call(_currentValue);
},
child: Container(
width: widget.isHorizontal ? 300 : 50,
height: widget.isHorizontal ? 50 : 300,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
),
child: CustomPaint(
painter: RulerPainter(
startValue: widget.startValue,
endValue: widget.endValue,
currentValue: _currentValue,
isHorizontal: widget.isHorizontal,
),
),
),
);
}
double _calculateValue(double position) {
final totalPixels = widget.isHorizontal
? 300
: 300; // 容器尺寸
final totalUnits = widget.endValue - widget.startValue;
final value = (position / totalPixels) * totalUnits + widget.startValue;
return value.clamp(widget.startValue, widget.endValue);
}
}
3. 使用示例
class RulerExample extends StatefulWidget {
@override
State<RulerExample> createState() => _RulerExampleState();
}
class _RulerExampleState extends State<RulerExample> {
double currentValue = 50;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('当前值: ${currentValue.toStringAsFixed(1)}'),
const SizedBox(height: 20),
CustomRuler(
startValue: 0,
endValue: 100,
onValueChanged: (value) {
setState(() {
currentValue = value;
});
},
),
const SizedBox(height: 20),
// 垂直尺子
CustomRuler(
startValue: 0,
endValue: 200,
isHorizontal: false,
onValueChanged: (value) {
setState(() {
currentValue = value;
});
},
),
],
);
}
}
主要特性
- 支持水平和垂直方向
- 可自定义数值范围
- 触摸交互选择数值
- 主刻度和次刻度显示
- 当前值指示器
你可以根据需要调整刻度样式、颜色、尺寸等参数来满足具体的设计需求。

