刻度和刻度值布局
与其将刻度线和刻度值行包装在一列中,不如将刻度线和刻度值组合为单个列可能更好。然后,将此列包裹起来Expanded
小部件并将其放入List.generate
一行。这保证了刻度始终与刻度值对齐,并且每列具有相等的间距。
刻度线和滑块对齐
默认情况下,滑块带有偏移量,以便为拇指和覆盖层腾出空间。即使您精确计算了屏幕上刻度之间的间距,但在不同屏幕尺寸上进行测试时,间距仍然会拉伸。
如果您有兴趣了解有关滑块偏移/边距的更多信息,请查看此链接。
滑块偏移问题 https://github.com/flutter/flutter/issues/37057
为了实现完美对齐,您需要执行以下操作
-
通过创建自定义来删除偏移量trackShape
的滑块。
-
测量一刻度间距的偏移量并将其除以 2。
公式为MediaQuery.of(context).size.width / numOfTick / 2
-
将零偏移滑块包裹起来Padding
widget 并使用计算出的偏移量作为水平填充值。padding: EdgeInsets.symmetric(horizontal: offset),
offset
V
|---|
.----------/ /------------.
| 0 | | 100 |
| |Ticks area | |
| | | | | ||
.----------/ /------------.|
| ||
| Slider area ||Screen edge
| ||
'----------/ /-------------'|
-
现在,无论屏幕尺寸如何,刻度线和滑块将始终从边到边完美对齐。
这是例子。您可能仍想根据您的要求改进 UI。
支持什么
- 可调整的主要和次要刻度
- 当值匹配时勾选突出显示
- 数值精度控制
double value = 50;
double actualValue = 50;
double minValue = 0;
double maxValue = 100;
List<double> steps = [0,5,10,15,20,25,30,35,40,45,50,60,70,80,90,100];
// ...
CustomSlider(
minValue: minValue,
maxValue: maxValue,
value: value,
majorTick: 6,
minorTick: 2,
labelValuePrecision: 0,
tickValuePrecision: 0,
onChanged: (val) => setState(() {
value = val;
actualValue =
steps[(val / maxValue * (steps.length - 1)).ceil().toInt()];
print('Slider value (linear): $value');
print('Actual value (non-linear): $actualValue');
}),
activeColor: Colors.orange,
inactiveColor: Colors.orange.shade50,
linearStep: false,
steps: steps,
),
自定义滑块小部件
class CustomSlider extends StatelessWidget {
final double value;
final double minValue;
final double maxValue;
final int majorTick;
final int minorTick;
final Function(double)? onChanged;
final Color? activeColor;
final Color? inactiveColor;
final int labelValuePrecision;
final int tickValuePrecision;
final bool linearStep;
final List<double>? steps;
CustomSlider({
required this.value,
required this.minValue,
required this.maxValue,
required this.majorTick,
required this.minorTick,
required this.onChanged,
this.activeColor,
this.inactiveColor,
this.labelValuePrecision = 2,
this.tickValuePrecision = 1,
this.linearStep = true,
this.steps,
});
@override
Widget build(BuildContext context) {
final allocatedHeight = MediaQuery.of(context).size.height;
final allocatedWidth = MediaQuery.of(context).size.width;
final divisions = (majorTick - 1) * minorTick + majorTick;
final double valueHeight =
allocatedHeight * 0.05 < 41 ? 41 : allocatedHeight * 0.05;
final double tickHeight =
allocatedHeight * 0.025 < 20 ? 20 : allocatedHeight * 0.025;
final labelOffset = allocatedWidth / divisions / 2;
return Column(
children: [
Row(
children: List.generate(
divisions,
(index) => Expanded(
child: Column(
children: [
Container(
alignment: Alignment.bottomCenter,
height: valueHeight,
child: index % (minorTick + 1) == 0
? Text(
linearStep
? '${(index / (divisions - 1) * maxValue).toStringAsFixed(tickValuePrecision)}'
: '${(steps?[index])?.toStringAsFixed(tickValuePrecision)}',
style: TextStyle(
fontSize: 12,
),
textAlign: TextAlign.center,
)
: null,
),
Container(
alignment: Alignment.bottomCenter,
height: tickHeight,
child: VerticalDivider(
indent: index % (minorTick + 1) == 0 ? 2 : 6,
thickness: 1.2,
color: (index / (divisions - 1)) * maxValue == value
? activeColor ?? Colors.orange
: Colors.grey.shade300,
),
),
],
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: labelOffset),
child: SliderTheme(
data: SliderThemeData(
trackHeight:
allocatedHeight * 0.011 < 9 ? 9 : allocatedHeight * 0.011,
activeTickMarkColor: activeColor ?? Colors.orange,
inactiveTickMarkColor: inactiveColor ?? Colors.orange.shade50,
activeTrackColor: activeColor ?? Colors.orange,
inactiveTrackColor: inactiveColor ?? Colors.orange.shade50,
thumbColor: activeColor ?? Colors.orange,
overlayColor: activeColor == null
? Colors.orange.withOpacity(0.1)
: activeColor!.withOpacity(0.1),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 12.0),
trackShape: CustomTrackShape(),
showValueIndicator: ShowValueIndicator.never,
valueIndicatorTextStyle: TextStyle(
fontSize: 12,
),
),
child: Slider(
value: value,
min: minValue,
max: maxValue,
divisions: divisions - 1,
onChanged: onChanged,
label: value.toStringAsFixed(labelValuePrecision),
),
),
),
],
);
}
}
class CustomTrackShape extends RoundedRectSliderTrackShape {
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
final double trackHeight = sliderTheme.trackHeight!;
final double trackLeft = offset.dx;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}
Test
编辑(具有非线性步骤的滑块)
由于 Flutter 滑块具有线性值-位置映射,因此基于非线性值更改滑块值可能不是一个好主意。但是,仍然可以通过为实际值创建附加值范围然后将它们映射在一起来实现这一点。
以下是步骤
- 创建一个包含非线性值的列表(您创建的列表)
- 确保列表的长度与刻度总数匹配。否则,映射将不正确。
listLength = (majorTick - 1) * minorTick + majorTick
已对上述代码进行编辑以解决此问题。
Test
映射值
Start End
Slider value (linear) 0.00 6.67 13.33 ... 86.67 93.33 100.0
Actual value (non-linear) 0.00 5.00 10.00 ... 80.00 90.00 100.0