如何编辑CustomTrackShape实现自定义轨道形状效果?
CustomTrackShape 自定义滑块轨道实现
我正在为项目设计CustomTrackShape自定义轨道形状,目标设计效果如下:
目前已完成大部分代码编写,当前实现效果如下:
侧边圆角问题后续会自行修复,当前还需要实现三点需求:
- 将label移动到轨道下方
- 尽可能支持调整label尺寸参数
- 自定义trackheight,实现轨道一端高度较小、另一端高度较大的效果
可直接复用的实现代码
所有需求点都已覆盖,相关参数全部开放可配置:
import 'package:flutter/material.dart'; class VariableHeightTrackShape extends SliderTrackShape with BaseSliderTrackShape { /// 轨道最小端高度 final double minTrackHeight; /// 轨道最大端高度 final double maxTrackHeight; /// 标签与轨道的垂直间距 final double labelTopGap; /// 标签文字样式 final TextStyle? labelStyle; /// 标签内边距 final Size labelPadding; const VariableHeightTrackShape({ this.minTrackHeight = 4, this.maxTrackHeight = 12, this.labelTopGap = 8, this.labelStyle, this.labelPadding = const Size(12, 6), }); @override Rect getPreferredRect({ required RenderBox parentBox, Offset offset = Offset.zero, required SliderThemeData sliderTheme, bool isEnabled = false, bool isDiscrete = false, }) { // 预留下方标签的绘制空间,避免内容被裁剪 final labelReserveHeight = (labelStyle?.fontSize ?? 12) + labelTopGap + labelPadding.height; final totalTrackAreaHeight = maxTrackHeight + labelReserveHeight; return Rect.fromLTWH( offset.dx, offset.dy + (parentBox.size.height - totalTrackAreaHeight) / 2, parentBox.size.width, totalTrackAreaHeight, ); } @override void paint( PaintingContext context, Offset offset, { required RenderBox parentBox, required SliderThemeData sliderTheme, required Animation<double> enableAnimation, required TextDirection textDirection, required Offset thumbCenter, Offset? secondaryOffset, bool isEnabled = false, bool isDiscrete = false, }) { final canvas = context.canvas; final trackTotalWidth = parentBox.size.width; // 轨道垂直居中,为下方标签留出空间 final trackVerticalCenter = offset.dy + maxTrackHeight / 2; final trackBottomPos = trackVerticalCenter + maxTrackHeight / 2; // 绘制变高度梯形轨道 _drawVariableHeightTrack( canvas: canvas, trackStart: offset.dx, trackEnd: offset.dx + trackTotalWidth, verticalCenter: trackVerticalCenter, activeColor: sliderTheme.activeTrackColor ?? Colors.blue, inactiveColor: sliderTheme.inactiveTrackColor ?? Colors.grey.shade300, thumbPosX: thumbCenter.dx, ); // 绘制轨道下方标签 _drawBottomLabel( canvas: canvas, thumbCenter: thumbCenter, sliderTheme: sliderTheme, trackBottom: trackBottomPos, ); } /// 绘制一端高一端低的梯形轨道 void _drawVariableHeightTrack({ required Canvas canvas, required double trackStart, required double trackEnd, required double verticalCenter, required Color activeColor, required Color inactiveColor, required double thumbPosX, }) { // 左侧未选中轨道:从左到右高度从min线性增长到max final inactiveTrackPath = Path() ..moveTo(trackStart, verticalCenter - minTrackHeight / 2) ..lineTo(thumbPosX, verticalCenter - maxTrackHeight / 2) ..lineTo(thumbPosX, verticalCenter + maxTrackHeight / 2) ..lineTo(trackStart, verticalCenter + minTrackHeight / 2) ..close(); canvas.drawPath(inactiveTrackPath, Paint()..color = inactiveColor); // 右侧选中轨道:从滑块位置到右端高度从max线性降到min final activeTrackPath = Path() ..moveTo(thumbPosX, verticalCenter - maxTrackHeight / 2) ..lineTo(trackEnd, verticalCenter - minTrackHeight / 2) ..lineTo(trackEnd, verticalCenter + minTrackHeight / 2) ..lineTo(thumbPosX, verticalCenter + maxTrackHeight / 2) ..close(); canvas.drawPath(activeTrackPath, Paint()..color = activeColor); // 侧边圆角逻辑可自行在此处补充 } /// 绘制轨道下方居中对齐滑块的标签 void _drawBottomLabel({ required Canvas canvas, required Offset thumbCenter, required SliderThemeData sliderTheme, required double trackBottom, }) { // 替换为实际需要显示的标签数值,可根据业务逻辑自定义 final labelText = sliderTheme.valueIndicatorFormatter?.call(0) ?? "0"; final textPainter = TextPainter( text: TextSpan( text: labelText, style: labelStyle ?? const TextStyle(fontSize: 12, color: Colors.black87), ), textDirection: TextDirection.ltr, )..layout(); // 标签坐标:水平居中对齐滑块,垂直位于轨道下方预留间距位置 final labelPos = Offset( thumbCenter.dx - (textPainter.width + labelPadding.width) / 2, trackBottom + labelTopGap, ); // 标签背景,不需要可删除 final labelBg = RRect.fromRectAndRadius( Rect.fromLTWH( labelPos.dx - labelPadding.width / 2, labelPos.dy - labelPadding.height / 2, textPainter.width + labelPadding.width, textPainter.height + labelPadding.height, ), const Radius.circular(4), ); canvas.drawRRect(labelBg, Paint()..color = Colors.white); textPainter.paint(canvas, labelPos); } }
调用示例
所有尺寸、样式参数都支持在初始化时自定义传入:
// 组件内使用 double _currentValue = 30; @override Widget build(BuildContext context) { return SliderTheme( data: SliderTheme.of(context).copyWith( trackShape: const VariableHeightTrackShape( minTrackHeight: 4, maxTrackHeight: 12, labelTopGap: 8, labelPadding: Size(10, 4), labelStyle: TextStyle(fontSize: 11, color: Colors.grey), ), // 其他滑块主题配置(滑块颜色、指示器样式等)照常设置即可 ), child: Slider( value: _currentValue, min: 0, max: 100, onChanged: (val) => setState(() => _currentValue = val), ), ); }
内容的提问来源于stack exchange,提问作者Abulkhair
相关产品推荐
相关产品推荐

