04_Flutter自定义Slider滑块
- IT业界
- 2025-07-21 19:24:50

04_Flutter自定义Slider滑块 一.Slider控件基本用法 Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Text( "sliderValue: ${_sliderValue.toInt()}" ), Slider( value: _sliderValue, min: 0, max: 100, divisions: 10, thumbColor: Colors.red, activeColor: Colors.red, onChanged: (value) { setState(() { _sliderValue = value; }); } ) ], ) const Slider({ super.key, required this.value, this.secondaryTrackValue, required this.onChanged, this.onChangeStart, this.onChangeEnd, this.min = 0.0, this.max = 1.0, this.divisions, this.label, this.activeColor, this.inactiveColor, this.secondaryActiveColor, this.thumbColor, this.overlayColor, this.mouseCursor, this.semanticFormatterCallback, this.focusNode, this.autofocus = false, })
几个比较重要的属性:
value:slider控件显示的值min:slider控件滑动到最左边对应的值,即最小值max: slider控件滑动到最右边对应的值,即最大值divisions: 最小值到最大值之间被几等分activeColor: 滑块划过部分的颜色值,即选中的颜色值inactiveColor:滑块未划过部分的颜色值,即为选中的颜色值thumbColor:滑块的颜色值 二.如何修改滑块的大小以及滑块轨迹的高度从上面的示例可以看到,通过Slider控件为我们提供的属性,只支持改变滑块的颜色,以及滑块轨迹的颜色,那么我们想要改变滑块的大小以及滑块轨迹的高度,是不是只能重新自定义呢?
NO! NO! NO!,细心的您在使用Flutter的AppBar时,可能会发现,为AppBar控件指定样式时,除了使用AppBar控件提供的属性外,也可以使用AppBarTheme来为AppBar设置某些特定的样式,既然如此,不妨查看下Flutter sdk的源码与Slider对应的是否有一个叫SliderTheme的控件呢? 嘿嘿,还真有。
final SliderThemeData data; const SliderTheme({ super.key, required this.data, required super.child, }); const SliderThemeData({ this.trackHeight, this.thumbShape, ... });仔细找SliderThemeData的trackHeight以及thumbShape的属性注释:
/// The height of the [Slider] track. final double? trackHeight; /// The shape that will be used to draw the [Slider]'s thumb. /// The default value is [RoundSliderThumbShape]. final SliderComponentShape? thumbShape;此处省略…翻译软件的时间:
trackHeight:[滑块]轨迹的高度thumbShape:默认值是一个RoundSliderThumbShape对象看下RoundSliderThumbShape的源码怎么写的:
const RoundSliderThumbShape({ this.enabledThumbRadius = 10.0, this.disabledThumbRadius, this.elevation = 1.0, this.pressedElevation = 6.0, });看到这里就不用做过多的解释了吧😂,因此要修改滑块的大小,可以重新指定thumbShape为RoundSliderThumbShape对象,并设置enabledThumbRadius的值。
Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Text( "sliderValue: ${_sliderValue.toInt()}" ), SliderTheme( data: const SliderThemeData( trackHeight: 20, thumbShape: RoundSliderThumbShape( enabledThumbRadius: 20 ) ), child: Slider( value: _sliderValue, min: 0, max: 100, divisions: 10, thumbColor: Colors.red, activeColor: Colors.red, onChanged: (value) { setState(() { _sliderValue = value; }); } ) ) ], ) 三.使用本地资源图片作为自定义滑块既然要自定义滑块,毫无疑问需要从SliderThemeData的thumbShape入手。
final SliderComponentShape? thumbShape;thumbShape的类型为SliderComponentShape,继续查看SliderComponentShape源码:
abstract class SliderComponentShape { const SliderComponentShape(); Size getPreferredSize(bool isEnabled, bool isDiscrete); 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, }); }因此我们可以定义一个类继承SliderComponentShape,并实现getPreferredSize和paint方法,getPreferredSize控制滑块大小,paint负责把滑块绘制到屏幕上。
首先第一步我们需要将本地图片为一个ImageInfo,例如传入一个"lib/assets/images/ic_slider_thumb.png",最后得到一个ImageInfo,这里就直接奉上源码了,其实现也是参考了Image.asset的源码: typedef AssertsWidgetBuilder = Widget Function(BuildContext context, ImageInfo? imageInfo); class AssertsImageBuilder extends StatefulWidget { final String assertsName; final AssertsWidgetBuilder builder; const AssertsImageBuilder( this.assertsName, { super.key, required this.builder, } ); @override State<StatefulWidget> createState() => _AssertsImageBuilderState(); } class _AssertsImageBuilderState extends State<AssertsImageBuilder> { ImageInfo? _imageInfo; @override void initState() { super.initState(); _loadAssertsImage().then((value) { setState(() { _imageInfo = value; }); }); } @override void didUpdateWidget(covariant AssertsImageBuilder oldWidget) { super.didUpdateWidget(oldWidget); if(oldWidget.assertsName != widget.assertsName) { _loadAssertsImage().then((value) { setState(() { _imageInfo = value; }); }); } } @override Widget build(BuildContext context) { return widget.builder!.call(context, _imageInfo); } Future<ImageInfo?> _loadAssertsImage() { final Completer<ImageInfo?> completer = Completer<ImageInfo?>(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final ImageProvider imageProvider = AssetImage(widget.assertsName); final ImageConfiguration config = createLocalImageConfiguration(context); final ImageStream stream = imageProvider.resolve(config); ImageStreamListener? listener; listener = ImageStreamListener( (ImageInfo? image, bool sync) { if (!completer.isCompleted) { completer plete(image); } SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { stream.removeListener(listener!); }); }, onError: (Object exception, StackTrace? stackTrace) { stream.removeListener(listener!); completer pleteError(exception, stackTrace); }, ); stream.addListener(listener); }); return completer.future; } } 自定义SliderComponentShape import 'package:flutter/material.dart'; import 'dart:ui' as ui; class ImageSliderThumb extends SliderComponentShape { final Size size; final ui.Image? image; const ImageSliderThumb({ required this.image, Size? size }): size = size ?? const Size(20, 20); @override Size getPreferredSize(bool isEnabled, bool isDiscrete) { return size; } @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}) { } } 绘制图片滑块 @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}) { //图片中心点 double dx = size.width/2; double dy = size.height/2; if(image != null) { final Rect sourceRect = Rect.fromLTWH(0, 0, image!.width.toDouble(), image!.width.toDouble()); //center会随着滑块的移动而改变,所以这里需要根据center计算图片绘制的位置 double left = center.dx - dx; double top = center.dy - dy; double right = center.dx + dx; double bottom = center.dy + dy; Rect destinationRect = Rect.fromLTRB(left, top, right, bottom); final Canvas canvas = context.canvas; final Paint paint = new Paint(); paint.isAntiAlias = true; //绘制滑块 canvas.drawImageRect(image!, sourceRect, destinationRect, paint); } } 四.怎么使用? Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Text( "sliderValue: ${_sliderValue.toInt()}" ), AssertsImageBuilder( "lib/assets/images/ic_slider_thumb.png", builder: (context, imageInfo) { return SliderTheme( data: SliderThemeData( trackHeight: 10, thumbShape: ImageSliderThumb( image: imageInfo?.image, size: const Size(30, 30) ) ), child: Slider( value: _sliderValue, min: 0, max: 100, divisions: 10, thumbColor: Colors.red, activeColor: Colors.red, onChanged: (value) { setState(() { _sliderValue = value; }); } ) ); } ), ], )04_Flutter自定义Slider滑块由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“04_Flutter自定义Slider滑块”
上一篇
机器学习的复习笔记2-回归
下一篇
QT配合CSS隐藏按钮