You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Flutter实现与参考图一致的自定义Slider组件需求

自定义Flutter Slider实现匹配设计样式

原生Flutter Slider的样式定制能力有限,要实现你需要的特殊样式,我们可以通过自定义SliderTheme结合自定义滑块组件来完成,或者更灵活地使用CustomPaint绘制完全自定义的轨道和滑块。

方案一:基于SliderTheme的定制(适合大部分场景)

通过修改SliderThemeData来调整轨道、滑块、刻度等样式,以下是匹配参考样式的示例代码:

class CustomStyledSlider extends StatelessWidget {
  final double value;
  final ValueChanged<double> onChanged;

  const CustomStyledSlider({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return SliderTheme(
      data: SliderTheme.of(context).copyWith(
        // 激活状态轨道渐变样式
        activeTrackColor: const LinearGradient(
          colors: [Colors.blueAccent, Colors.purpleAccent],
          begin: Alignment.centerLeft,
          end: Alignment.centerRight,
        ).createShader(const Rect.fromLTWH(0, 0, 300, 4)),
        // 未激活状态轨道颜色
        inactiveTrackColor: Colors.grey.shade300,
        // 轨道高度
        trackHeight: 6,
        // 自定义滑块形状
        thumbShape: CustomSliderThumbShape(),
        // 轨道两端圆角
        trackShape: const RoundedRectSliderTrackShape(
          trackRadius: 3,
        ),
      ),
      child: Slider(
        value: value,
        min: 0,
        max: 100,
        onChanged: onChanged,
      ),
    );
  }
}

// 自定义滑块形状
class CustomSliderThumbShape extends SliderComponentShape {
  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return const Size(24, 24);
  }

  @override
  void paint(
    PaintingContext context,
    Offset center, {
    required Animation<double> activationAnimation,
    required Animation<double> enableAnimation,
    required bool isDiscrete,
    required TextPainter labelPainter,
    required RenderBox parentBox,
    required SliderThemeData sliderTheme,
    required TextDirection textDirection,
    required double value,
    required double textScaleFactor,
    required Size sizeWithOverflow,
  }) {
    final canvas = context.canvas;
    // 绘制滑块阴影
    canvas.drawCircle(
      center,
      12,
      Paint()
        ..color = Colors.black.withOpacity(0.1)
        ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4),
    );
    // 绘制滑块外层白色圆
    canvas.drawCircle(
      center,
      10,
      Paint()..color = Colors.white,
    );
    // 绘制滑块内层蓝色指示器
    canvas.drawCircle(
      center,
      6,
      Paint()..color = Colors.blueAccent,
    );
  }
}

方案二:完全自定义绘制(适合极致定制需求)

如果参考样式有更特殊的轨道或交互逻辑,可以用CustomPaint从头绘制:

class FullyCustomSlider extends StatefulWidget {
  final double value;
  final ValueChanged<double> onChanged;

  const FullyCustomSlider({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  State<FullyCustomSlider> createState() => _FullyCustomSliderState();
}

class _FullyCustomSliderState extends State<FullyCustomSlider> {
  late double _currentValue;

  @override
  void initState() {
    super.initState();
    _currentValue = widget.value;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onHorizontalDragUpdate: (details) {
        final box = context.findRenderObject() as RenderBox;
        final localPos = box.globalToLocal(details.globalPosition);
        final trackWidth = box.size.width - 24; // 减去滑块宽度
        final newValue = (localPos.dx - 12) / trackWidth;
        final clampedValue = newValue.clamp(0.0, 1.0) * 100;
        setState(() => _currentValue = clampedValue);
        widget.onChanged(clampedValue);
      },
      onTapDown: (details) {
        final box = context.findRenderObject() as RenderBox;
        final localPos = box.globalToLocal(details.globalPosition);
        final trackWidth = box.size.width - 24;
        final newValue = (localPos.dx - 12) / trackWidth;
        final clampedValue = newValue.clamp(0.0, 1.0) * 100;
        setState(() => _currentValue = clampedValue);
        widget.onChanged(clampedValue);
      },
      child: CustomPaint(
        size: const Size(double.infinity, 40),
        painter: _CustomSliderPainter(value: _currentValue),
      ),
    );
  }
}

class _CustomSliderPainter extends CustomPainter {
  final double value;

  _CustomSliderPainter({required this.value});

  @override
  void paint(Canvas canvas, Size size) {
    final trackHeight = 6.0;
    final trackY = size.height / 2 - trackHeight / 2;
    final trackWidth = size.width - 24;
    final activeWidth = trackWidth * (value / 100);

    // 绘制未激活轨道
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(12, trackY, trackWidth, trackHeight),
        const Radius.circular(3),
      ),
      Paint()..color = Colors.grey.shade300,
    );

    // 绘制激活渐变轨道
    final gradient = LinearGradient(
      colors: [Colors.blueAccent, Colors.purpleAccent],
      begin: Alignment.centerLeft,
      end: Alignment.centerRight,
    );
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(12, trackY, activeWidth, trackHeight),
        const Radius.circular(3),
      ),
      Paint()..shader = gradient.createShader(Rect.fromLTWH(12, trackY, activeWidth, trackHeight)),
    );

    // 绘制滑块
    final sliderX = 12 + activeWidth;
    final sliderY = size.height / 2;

    // 滑块阴影
    canvas.drawCircle(
      Offset(sliderX, sliderY),
      12,
      Paint()
        ..color = Colors.black.withOpacity(0.1)
        ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4),
    );
    // 滑块外层
    canvas.drawCircle(
      Offset(sliderX, sliderY),
      10,
      Paint()..color = Colors.white,
    );
    // 滑块内层指示器
    canvas.drawCircle(
      Offset(sliderX, sliderY),
      6,
      Paint()..color = Colors.blueAccent,
    );
  }

  @override
  bool shouldRepaint(covariant _CustomSliderPainter oldDelegate) {
    return oldDelegate.value != value;
  }
}

使用时直接调用组件即可:

CustomStyledSlider(
  value: _sliderValue,
  onChanged: (val) => setState(() => _sliderValue = val),
)

你可以根据参考图的具体细节调整颜色、尺寸、渐变方向等参数,比如修改滑块的形状、轨道的圆角大小或者添加刻度标记。

内容的提问来源于stack exchange,提问作者Ahsan Ilyas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 09:31:04