点击上方蓝字关注我,知识会给你力量
Slider是Flutter中使用非常多的一个组件,通常设计师都会对Slider做很多的自定义设计,在Android中,我们其实是很难通过配置xml来改变Slider的外观的,而在Flutter中,我们可以很方便的组合整个实现,当然,前提是你需要对Slider的整体概念有个清晰的认识。
下面这张图是Slider的一个基本组成,这里已经融合了一些基础的设计元素,所以,将它作为一个整体,是不错的选择。
它主要组件定义如下:
thumb:是用户拖动时水平滑动的Shape。
track:是Sliderthumb滑动的线。
overlay:拖动时按下thumb时出现的光晕效果。
tick marks:使用离散分割时绘制的有规律间隔的标记。
value indicator:用户拖动thumb时出现,指示所选的值。
我们通过一个Flutter中的默认Slider来开始我们的改造之旅。
SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.red[700],
inactiveTrackColor: Colors.red[100],
trackShape: const RoundedRectSliderTrackShape(),
trackHeight: 4.0,
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12.0),
thumbColor: Colors.redAccent,
overlayColor: Colors.red.withAlpha(32),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 28.0),
tickMarkShape: const RoundSliderTickMarkShape(),
activeTickMarkColor: Colors.red[700],
inactiveTickMarkColor: Colors.red[100],
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
valueIndicatorColor: Colors.redAccent,
valueIndicatorTextStyle: const TextStyle(color: Colors.white),
),
child: Slider(
value: _value,
min: 0,
max: 100,
divisions: 10,
label: '$_value',
onChanged: (value) {
setState(() => _value = value);
},
),
)
效果如下:
我们不难发现,对于上文讨论的每个组件,SliderThemeData 对象都会采用某种Shape。 此外,它还包含活动轨迹颜色(activeTrackColor)、overlay颜色(overlayColor)、轨迹高度(trackHeight)等参数,这些参数可以帮助我们自定义组件的某个方面。 虽然不言自明,但我们还是要更好地理解其中的一些属性。
activeTrackColor : activeTrack 通常是从最小值到大thumb的轨道边。 我们应为此属性指定一个颜色对象。
inactiveTrackColor : 非活动轨迹通常是从thumb到最大值的轨迹。 我们将为该属性指定一个颜色对象。
trackShape:接收Sliderthumb滑动轨道的Shape。 RoundedRectSliderTrackShape 是隐式分配给它的。 稍后我们将讨论如何自定义该Shape。
trackHeight:以像素为单位定义轨道的高度。 接收一个 double 类型的值。
thumbShape : 接收thumb的Shape。 RoundSliderThumbShape 已隐式赋值给它。 我们可以通过传递半径值来改变thumb的大小。 我们将自定义此Shape,以实现最初的目标。
OverlayShape:thumb后面的光晕效果Shape。 RoundSliderOverlayShape 已隐式分配给它。 可以将半径值传递给Shape。
tickMarkShape:在Slider中,通过离散值选择显示刻度线。 这些标记也是可以自定义的Shape。 RoundSliderTickMarkShape 是隐式赋值。 同样,半径值也可以传递给它。
valueIndicatorShape:当用户拖动thumb时,valueIndicator 就会显示,以指示所选的值。 这有助于设置显示值的Shape。PaddleSliderValueIndicatorShape 是Slider值指示器的默认Shape。
valueIndicatorTextStyle :该参数用于为指示器上显示的文本设置 TextStyle。
这并不是 SliderThemeData 类所有属性的详尽列表。 其中提到的属性涵盖了大多数使用情况。 其他大部分属性用于处理部件的禁用状态。
thumb, track, overlay, tick marks,所有这些组件都只是Shape而已。 Material Slider的默认thumbShape是 RoundSliderThumbShape,这是用于构建thumb的默认类。 在查看该类的源代码时,我们会发现该类使用Canvas来绘制Shape。 这意味着我们几乎可以绘制任何自定义Shape、文本等。 它属于 Flutter 的自定义绘画领域。,只是略有不同。 它扩展了 SliderComponentShape 类。
这里值得注意的是 2 个重载方法,即 getPreferredSize 和 paint 方法。
paint 方法直接用于自定义绘制。 由于我们扩展的是 SliderComponentShape 类,因此绘制方法会接收所有相关数据,以便构建thumb、overlay或任何其他扩展 SliderComponentShape 类的组件。 我们不会再浪费时间解释单个属性,因为示例总是比概念更好。 因此,让我们通过创建开始时的示例来学习实际的生产代码。
Slider with Continuous Values
Slider with Discrete Divisions.
Slider with Rectangular Thumb
这正是我们从一开始就想制作的Slider。 但与基本Slider相比,它们有什么特别之处呢? 有以下几点:
thumb本身包含一个始终可见的数值指示器,与内置数值指示器不同的是,内置数值指示器只有在指针被拖动或按下时才会弹出。
示例中显示的是圆角矩形thumb。
只显示track,track的活动边和非活动边有不同的颜色。 为此,我们将活动和非活动轨迹颜色的不透明度都设置为零。
slider被放置在具有圆角和渐变背景的矩形内。 这是通过将Slider包裹在一个带有 borderRadius 和 LinearGradient 的容器中实现的。
我们先来看看带数值指示器的Thumb。
classCustomSliderThumbCircleextendsSliderComponentShape {
final double thumbRadius;
final int min;
final int max;
constCustomSliderThumbCircle({
required this.thumbRadius,
this.min = 0,
this.max = 10,
});
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.fromRadius(thumbRadius);
@override
voidpaint(
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 doublevalue,
required double textScaleFactor,
required Size sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final paint = Paint()
..color = Colors.white //Thumb Background Color
..style = PaintingStyle.fill;
TextSpan span = TextSpan(
style: TextStyle(
fontSize: thumbRadius * .8,
fontWeight: FontWeight.w700,
color: sliderTheme.thumbColor, //Text Color of Value on Thumb
),
text: getValue(value),
);
TextPainter tp = TextPainter(text: span, textAlign: TextAlign.center, textDirection: TextDirection.ltr);
tp.layout();
Offset textCenter = Offset(center.dx - (tp.width / 2), center.dy - (tp.height / 2));
canvas.drawCircle(center, thumbRadius * .9, paint);
tp.paint(canvas, textCenter);
}
String getValue(doublevalue) {
return (min + (max - min) * value).round().toString();
}
}
Custom Thumb的属性包括thumbRadius、Slider的最小值和最大值,这些都是在Thumb上绘制所必需的。
我们知道,实际的自定义绘制是在 paint() 中进行的。 因此,它也为我们提供了绘制组件所需的所有相关数据。 其中一些重要数据包括:
context:我们使用上下文提取Canvas。
center:帮助我们对齐组件Shape。
isDiscrete:布尔值,表明Slider是否使用divisions。
sliderTheme:对我们用Slider包装的 SliderThemeData 对象的引用,我们可以从中提取重要的主题数据。 我们可以从中提取关键的主题数据。
value : 给出Slider的值,归一化为 0.0 至 1.0 的范围。 我们可以根据范围轻松更改原点。
Canvas:是我们的画板。
paint:是我们的画笔,我们可以使用填充、描边等。 也可用于设置文本样式。
上面代码中的一些要点可能需要解释一下:
创建paint对象是为了向Shape填充颜色。 我们还可以用它来添加描边等。
我们使用名为 getValue() 的方法将 paint() 中的归一化值转换为我们的范围。
我们需要找到一个偏移量来将文本绘制在绝对中心。 因此需要额外的代码来计算出正确的文本中心偏移量。
下面是使用 paint() 中的中心偏移量作为文本偏移量时的效果。
然后使用 canvas.draw 函数绘制Shape,之后使用 textPainter 对象绘制文本。
下面是圆角矩形thumb的代码
classCustomSliderThumbRectextendsSliderComponentShape{
finaldouble thumbRadius;
finalint thumbHeight;
finalint min;
finalint max;
constCustomSliderThumbRect({
required this.thumbRadius,
required this.thumbHeight,
required this.min,
required this.max,
});
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete)=> Size.fromRadius(thumbRadius);
@override
voidpaint(
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 canvas = context.canvas;
final rRect = RRect.fromRectAndRadius(
Rect.fromCenter(center: center, width: thumbHeight * 1.2, height: thumbHeight * .6),
Radius.circular(thumbRadius * .4),
);
final paint = Paint()
..color = sliderTheme.activeTrackColor! //Thumb Background Color
..style = PaintingStyle.fill;
TextSpan span = TextSpan(
style: TextStyle(fontSize: thumbHeight * .3, fontWeight: FontWeight.w700, color: sliderTheme.thumbColor, height: 1),
text: getValue(value),
);
TextPainter tp = TextPainter(text: span, textAlign: TextAlign.left, textDirection: TextDirection.ltr);
tp.layout();
Offset textCenter = Offset(center.dx - (tp.width / 2), center.dy - (tp.height / 2));
canvas.drawRRect(rRect, paint);
tp.paint(canvas, textCenter);
}
String getValue(double value){
return (min + (max - min) * value).round().toString();
}
}
为简洁起见,我们无法创建所有组件Shape的自定义实现。 但我可以肯定的是,本文已经提供了足够的信息,让你可以自定义 Material Slider Widget 的每一个微小部分。 下面我将附上Slider本身的代码。
classSliderWidgetextendsStatefulWidget {
final double sliderHeight;
final int min;
final int max;
final bool fullWidth;
const SliderWidget({
super.key,
this.sliderHeight = 48,
this.max = 10,
this.min = 0,
this.fullWidth = false,
});
@override
SliderWidgetState createState() => SliderWidgetState();
}
classSliderWidgetStateextendsState<SliderWidget> {
double _value = 0;
@override
Widget build(BuildContext context) {
double paddingFactor = .2;
if (widget.fullWidth) paddingFactor = .3;
return Container(
width: widget.fullWidth ? double.infinity : (widget.sliderHeight) * 5.5,
height: (widget.sliderHeight),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular((widget.sliderHeight * .3))),
gradient: const LinearGradient(
colors: [
Color(0xFF00c6ff),
Color(0xFF0072ff),
],
begin: FractionalOffset(0.0, 0.0),
end: FractionalOffset(1.0, 1.00),
stops: [0.0, 1.0],
tileMode: TileMode.clamp,
),
),
child: Padding(
padding: EdgeInsets.fromLTRB(widget.sliderHeight * paddingFactor, 2, widget.sliderHeight * paddingFactor, 2),
child: Row(
children: <Widget>[
Text(
'
${widget.min}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: widget.sliderHeight * .3,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
SizedBox(width: widget.sliderHeight * .1),
Expanded(
child: Center(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.white.withOpacity(1),
inactiveTrackColor: Colors.white.withOpacity(.5),
trackHeight: 4.0,
thumbShape: CustomSliderThumbRect(
thumbRadius: widget.sliderHeight * .4,
min: widget.min,
max: widget.max,
thumbHeight: 30,
),
overlayColor: Colors.white.withOpacity(.4),
activeTickMarkColor: Colors.white,
inactiveTickMarkColor: Colors.red.withOpacity(.7),
),
child: Slider(
value: _value,
onChanged: (value) {
setState(() => _value = value);
}),
),
),
),
SizedBox(width: widget.sliderHeight * .1),
Text(
'$
{widget.max}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: widget.sliderHeight * .3,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
],
),
),
);
}
}
翻译并修改自:https://medium.com/flutter-community/flutter-sliders-demystified-4b3ea65879c
向大家推荐下我的网站 https://www.yuque.com/xuyisheng 点击原文一键直达
专注 Android-Kotlin-Flutter 欢迎大家访问
往期推荐
本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。
< END >
作者:徐宜生
更文不易,点个“三连”支持一下👇