前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter Slider 挂件:配合案例理解

Flutter Slider 挂件:配合案例理解

作者头像
Jimmy_is_jimmy
发布2024-03-17 08:31:36
1680
发布2024-03-17 08:31:36
举报
文章被收录于专栏:call_me_Rcall_me_R

原文链接:Flutter Slider widgets: A deep dive with example - 原文作者 Souvik Biswas

本文采用意译的方式

Slider 是一个基本的 Flutter 挂件 - 可以通过移动 slider 的滑块来选择范围值。在 Flutter 中,有不同类型的 slider 挂件,Flutter 框架中常用的有:

  • Slider - 一个 Material Design 组件,它允许你在一个范围值中选中一个值(存在一个滑块 slider thumb
  • CupertinoSlider - 和 Slider 相似,但是属于 Cupertino 设计风格。
  • RangeSlider - 在指定范围值中,用来选择一个范围(使用两个滑块)

本文,我们将会学到:

  • 在我们的 Flutter App 中,如何使用这些基本的挂件
  • 通过添加颜色和应用主题,如何自定义它们
  • 使用 CustomPainter,如何自定义 slider 挂件设计

现在,我们进入正题。

开始

最基本的 Slider 格式形式如下:

实现上面效果的相关代码如下:

代码语言:javascript
复制
Slider(
  min: 0.0,
  max: 100.0,
  value: _value,
  onChanged: (value) {
    setState(() {
      _value = value;
    });
  },
)

widget class 中,_value 会被初始化:

代码语言:javascript
复制
double _value = 20;

上面我设置的属性,是我们使用 Flutter 构建任何 slider 至少需要用到的属性,但是,不同的 slider,属性可能有点不同。我们看看这些属性:

  • min:用户可以拖动 slider 到左边的最小值(越靠 slider 的左边,数值越小)
  • max:用户可以拖动 slider 到右边的最大值(越靠 slider 的右边,数值越大)
  • value:用户通过拖动滑块获取到的 slider 当前值
  • onChanged:这是个回调函数,当在 slider 轨道上往左或往右拖动滑块,将会调用该函数并返回当前 slider 的位置值

onChanged 内部,我们通过 setState 来更新 _value 变量:

代码语言:javascript
复制
setState(() {
  _value = value;
});

这里,setState 用来更新 UI,以便于每次更新值都能在 slider 挂件中反映出来。需要注意的是,Slider 应该在 StatefulWidget 中使用 setState

这个基本的 slider 挂件使用了 Material Design 风格,这很适合 Android devices,而 iOS 设备趋向于使用 Cupertino 风格。在 iOS devices 上,更趋向于使用 CupertinoSlider

我们可以通过用 CupertinoSlider 挂件替换 Slider 挂件来实现 iOS-styleslider,它们的属性和上面案例的完全一样。

slider 的效果如下:

相关的代码如下:

代码语言:javascript
复制
Container(
  width: double.maxFinite,
  child: CupertinoSlider(
    min: 0.0,
    max: 100.0,
    value: _value,
    onChanged: (value) {
      setState(() {
        _value = value;
      });
    },
  ),
)

默认的,Cupertino Slider 不会占用整个屏幕的宽度,所以我们得用 Container 挂件来包含它,如果我们想让其占满屏幕的宽度,需要提供一个值为 double.maxFinite 的宽度。

SliderCupertinoSlider 都只允许我们在指定的范围选定一个值,但是,如果我们想选中两个值,可以考虑使用 RangeSlider 挂件。

RangeSlider 挂件

RangeSlider 挂件也是遵循 Material Design 风格,它有两个滑块,控制开始值和结束值。在这个挂件中,没有 value 属性;相反的,有 values 属性,类型是 RangeValues

基本的 RangeSlider 挂件长这样👇

相关代码如下:

代码语言:javascript
复制
RangeSlider(
  min: 0.0,
  max: 100.0,
  values: RangeValues(_startValue, _endValue),
  onChanged: (values) {
    setState(() {
      _startValue = values.start;
      _endValue = values.end;
    });
  },
)

RangeValues 需要两个输入值:开始值(_startValue 提供)和结束值(_endValue 提供)。我们可以在 widget class 内定义这两个变量,就像下面这样:

代码语言:javascript
复制
double _startValue = 20.0;
double _endValue = 90.0;

通过这些值来运行应用,slider 两滑块将会被赋予初始值。在 Range Slider 中,回调函数 onChanged 也会返回 RangeValues,方便我们用来更新两滑块的位置:

代码语言:javascript
复制
setState(() {
  _startValue = values.start;
  _endValue = values.end;
});

自定义 slider 颜色

上面我们讨论的三个 slider 挂件,都有一些属性供我们自定义其颜色。

基础的 Slider 挂件有三个属性来设置颜色:

  • activeColor:将颜色应用到滑块轨道的活动部分
  • inactiveColor:将颜色应用到滑块轨道的非活动部分
  • thumbColor:将颜色应用在滑块(指示块)

我们可以实现这个 Slider 颜色组合,使用下面的代码👇

代码语言:javascript
复制
Slider(
  min: 0.0,
  max: 100.0,
  activeColor: Colors.purple,
  inactiveColor: Colors.purple.shade100,
  thumbColor: Colors.pink,
  value: _value,
  onChanged: (value) {
    setState(() {
      _value = value;
    });
  },
)

相似的,我们可以更改这些属性,来自定义 Slider 颜色,下面就是些例子:

如果我们使用 CupertinoSlider 挂件,我们只需要自定义两个颜色属性:

  • activeColor
  • thumbColor

下面就是一个自定义 Cupertino 挂件的例子:

我们可以使用下面的代码来构建上面自定义 iOS-styleslider

代码语言:javascript
复制
Container(
  width: double.maxFinite,
  child: CupertinoSlider(
    min: 0.0,
    max: 100.0,
    value: _value,
    activeColor: CupertinoColors.activeGreen,
    thumbColor: CupertinoColors.systemPink,
    divisions: 10,
    onChanged: (value) {
      setState(() {
        _value = value;
      });
    },
  ),
)

当然,RangeSlider 挂件也允许我们使用两个属性自定义,但是和 Cupertino Slider 有个不同:

  • activeColor
  • inactiveColor

下面是一个自定义 Range Slider 的例子:

上面👆的 slider 通过下面的代码构建:

代码语言:javascript
复制
RangeSlider(
  min: 0.0,
  max: 100.0,
  activeColor: widget.activeColor,
  inactiveColor: widget.inactiveColor,
  values: RangeValues(_startValue, _endValue),
  onChanged: (values) {
    setState(() {
      _startValue = values.start;
      _endValue = values.end;
    });
  },
)

接下来,我们将讨论更加复杂的自定义场景,然后将其应用在 sliders 上。

显示 slider 的分割线和标签

通常的,slider 挂件是返回小数的值,因为它们默认是连续的。但是,如果我们只需要离散的值(即,没有任何小数位的整数),可以使用属性 divisions

label 属性通常被用来和离散的值配合使用。会在滑块上显示选中的值。

当设定 divisionslabel 属性后,基本的 Slider 挂件可能像下面这样:

代码如下:

代码语言:javascript
复制
Slider(
  min: 0.0,
  max: 100.0,
  value: _value,
  divisions: 10,
  label: '${_value.round()}',
  onChanged: (value) {
    setState(() {
      _value = value;
    });
  },
)

CupertinoSlider 挂件中,我们可以设定 divisions,但是不支持属性 label

RangeSlider 挂件有和 Slider 挂件相似的属性:divisions 是用来展示离散的值,labels 将会被使用,因为有两个滑块(指示器)。labels 属性将赋予类型为 RangeLabels 的值。

在应用了 divisonslabels 之后,Range Slider 就像下面这样:

相关代码如下:

代码语言:javascript
复制
RangeSlider(
  min: 0.0,
  max: 100.0,
  divisions: 10,
  labels: RangeLabels(
    _startValue.round().toString(),
    _endValue.round().toString(),
  ),
  values: RangeValues(_startValue, _endValue),
  onChanged: (values) {
    setState(() {
      _startValue = values.start;
      _endValue = values.end;
    });
  },
)

展示 slider 的状态

在一些场景中,我们需要知道 slider 当前的状态(它是否是空闲的,是否已经被拖动过,是否正在被拖动)。三种 slider 都有一些对应的回调函数帮我们实现。如下:

  • onChanged:当用户拖动滑块,就会调用,并更新其值
  • onChangeStart:当用户开始拖拽时回调。这个回调用来表明用户已经开始拖动,可以被用来更新任何相关的 UI
  • onChangeEnd:当用户停止拖拽时回调。这个回调用来表明用户已经停止拖动,可以被用来更新任何相关的 UI

上面列出的三个回调,只有 onChanged 应该被用来更新 slider 值。

这里是一个简单的例子,用户用这三个回调函数来更新 Text 挂件:

上面动图相关代码如下:

代码语言:javascript
复制
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Slider(
      min: 0.0,
      max: 100.0,
      value: _value,
      divisions: 10,
      onChanged: (value) {
        setState(() {
          _value = value;
          _status = 'active (${_value.round()})';
          _statusColor = Colors.green;
        });
      },
      onChangeStart: (value) {
        setState(() {
          _status = 'start';
          _statusColor = Colors.lightGreen;
        });
      },
      onChangeEnd: (value) {
        setState(() {
          _status = 'end';
          _statusColor = Colors.red;
        });
      },
    ),
    Text(
      'Status: $_status',
      style: TextStyle(color: _statusColor),
    ),
  ],
)

在类的内部,会初始化一些变量,如下:

代码语言:javascript
复制
double _value = 20;
String _status = 'idle';
Color _statusColor = Colors.amber;

这些变量会根据回调函数更新,并且通过 setState 调用来更新 Text 挂件。

给 sliders 应用主题

现在,我们更深入一些自定义 sliders 的内容。我们可以通过用 SliderTheme 包裹 Slider 挂件来解锁这些自定义内容,这可以让我们通过具体的属性来自定义 slider 各个方面。

让我们构建下面的 slider

SliderTheme 有很多的属性,但是我们构建上面效果的属性只需如下:

  • trackHeight:指定整个轨道的高度,不管是活跃或者非活跃的轨道部分
  • trackShape:指定轨道的两端是否是圆形的,应用在活跃或者非活跃的轨道部分。使用 RoundedRectSliderTrackShape 可以有一个很好的圆形边界。
  • activeTrackColor:指定轨道活跃部分的颜色,在上面的例子中是最左部分,从滑块最小值位置到滑块当前值位置
  • inactiveTrackColor:指定轨道非活跃部分的颜色,在上面的例子中是最右边部分,从滑块当前值位置到到最大值位置
  • thumbShape:指定 slider thumb 的形状。RoundSliderThumbShape 表明是一个完全圆形的 thumbthumb 的大小及其按压的高度都可以在这里设置
  • thumbColor:指定 slider thumb 应用的颜色
  • overlayColor:指定 slider thumb 被按压时候,其旁边可见的蒙层的颜色,该蒙层是透明的
  • overlayShape:指定蒙层的形状和其圆角
  • tickMarkShape:轨道上的指示分割点,指定应用在滑块轨道蒙层上的形状。当滑块有分割点的时候可见
  • activeTickMarkColor:指定在轨道活跃部分的分割点的颜色
  • inactiveTickMarkColor:指定在轨道非活跃部分的分割点的颜色
  • valueIndicatorShape:指定指示点值的形状,其含有标签(比如文本值),当 slider thumb 处于按压的状态其可见
  • valueIndicatorColor:指定指示点值的颜色。通常,会应用接近 slider thumb 的颜色,理论上你可以指定任何颜色
  • valueIndicatorTextStyle:指定滑块中指示点值文本的样式

完整的代码如下:

代码语言:javascript
复制
SliderTheme(
  data: SliderTheme.of(context).copyWith(
    trackHeight: 10.0,
    trackShape: RoundedRectSliderTrackShape(),
    activeTrackColor: Colors.purple.shade800,
    inactiveTrackColor: Colors.purple.shade100,
    thumbShape: RoundSliderThumbShape(
      enabledThumbRadius: 14.0,
      pressedElevation: 8.0,
    ),
    thumbColor: Colors.pinkAccent,
    overlayColor: Colors.pink.withOpacity(0.2),
    overlayShape: RoundSliderOverlayShape(overlayRadius: 32.0),
    tickMarkShape: RoundSliderTickMarkShape(),
    activeTickMarkColor: Colors.pinkAccent,
    inactiveTickMarkColor: Colors.white,
    valueIndicatorShape: PaddleSliderValueIndicatorShape(),
    valueIndicatorColor: Colors.black,
    valueIndicatorTextStyle: TextStyle(
      color: Colors.white,
      fontSize: 20.0,
    ),
  ),
  child: Slider(
    min: 0.0,
    max: 100.0,
    value: _value,
    divisions: 10,
    label: '${_value.round()}',
    onChanged: (value) {
      setState(() {
        _value = value;
      });
    },
  ),
)

SliderTheme 上还有很多其他的属性,但是这些属性够大多数用户进行自定义了。感兴趣,可以尝试其他的属性,你可以走得更远。

通过 CustomPainter设计自定义 sliders

SliderTheme 允许我们从 Flutter 预设的设计修改滑块组件。如果你想在滑块上给盒外面定制款式,CustomPainter 就会派上用场。

我们可以为不同的 slider 组件创建自己的设计(比如分割标记,slider thumb,滑轨等),并且为这些组件分配它们形状。

如下,我们将在 Slider 挂件上创建 slider thumb 自定义形状👇

为了创建该多边形的 slider thumb,我们需要在继承 SliderComponentShape 类的子类中去生成这个形状:

代码语言:javascript
复制
class PolygonSliderThumb extends SliderComponentShape {
  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    // Define size here
  }

  @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,
  }) {
    // Define the slider thumb design here
  }
}

当继承 SliderComponentShape 类,我们需要重写下面两个方法:

  1. getPreferredSize():这个方法返回 slider thumb 形状的大小
  2. paint():这个方法生成 slider thumb 的设计

我们需要给 PolygonSliderThumb 类传递两个值,thumb 半径的值和当前滑块选中的值:

代码语言:javascript
复制
class PolygonSliderThumb extends SliderComponentShape {
  final double thumbRadius;
  final double sliderValue;

  const PolygonSliderThumb({
    required this.thumbRadius,
    required this.sliderValue,
  });

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return Size.fromRadius(thumbRadius);
  }

  // ...
}

在这里,我们使用了 thumbRadius 变量来定义 slider thumb 形状的半径。

现在,让我们在 paint() 方法上定义形状。这跟我们上面用到的 CustomPainter 很类似,它俩都有相同的概念:

  • canvas:绘制和创建我们想要的形状的画布
  • paint:我们用来绘制的画笔

我们可以通过 context 来获取到 canvas 对象,并且传递给 paitn() 方法:

代码语言:javascript
复制
final Canvas canvas = context.canvas;

给多边形自定义些常量,比如多边形的变,其内部或者外部的半径,最后计算其角度:

代码语言:javascript
复制
int sides = 4;
double innerPolygonRadius = thumbRadius * 1.2;
double outerPolygonRadius = thumbRadius * 1.4;
double angle = (math.pi * 2) / sides;

绘制操作的顺序应该如下:

  1. 外层路径
  2. 内层路径
  3. 文本值

第一点是开始绘制,第二点紧随,第三点最后。

绘制外层路径,我们可以使用下面的代码:

代码语言:javascript
复制
final outerPathColor = Paint()
  ..color = Colors.pink.shade800
  ..style = PaintingStyle.fill;

var outerPath = Path();

Offset startPoint2 = Offset(
  outerPolygonRadius * math.cos(0.0),
  outerPolygonRadius * math.sin(0.0),
);

outerPath.moveTo(
  startPoint2.dx + center.dx,
  startPoint2.dy + center.dy,
);

for (int i = 1; i <= sides; i++) {
  double x = outerPolygonRadius * math.cos(angle * i) + center.dx;
  double y = outerPolygonRadius * math.sin(angle * i) + center.dy;
  outerPath.lineTo(x, y);
}

outerPath.close();
canvas.drawPath(outerPath, outerPathColor);

绘制内层路径,可以使用如下代码:

代码语言:javascript
复制
final innerPathColor = Paint()
  ..color = sliderTheme.thumbColor ?? Colors.black
  ..style = PaintingStyle.fill;

var innerPath = Path();

Offset startPoint = Offset(
  innerPolygonRadius * math.cos(0.0),
  innerPolygonRadius * math.sin(0.0),
);

innerPath.moveTo(
  startPoint.dx + center.dx,
  startPoint.dy + center.dy,
);

for (int i = 1; i <= sides; i++) {
  double x = innerPolygonRadius * math.cos(angle * i) + center.dx;
  double y = innerPolygonRadius * math.sin(angle * i) + center.dy;
  innerPath.lineTo(x, y);
}

innerPath.close();
canvas.drawPath(innerPath, innerPathColor);

最后,使用下面代码绘制文本值:

代码语言:javascript
复制
TextSpan span = new TextSpan(
  style: new TextStyle(
    fontSize: thumbRadius,
    fontWeight: FontWeight.w700,
    color: Colors.white,
  ),
  text: sliderValue.round().toString(),
);

TextPainter tp = new TextPainter(
  text: span,
  textAlign: TextAlign.center,
  textDirection: TextDirection.ltr,
);

tp.layout();

Offset textCenter = Offset(
  center.dx - (tp.width / 2),
  center.dy - (tp.height / 2),
);

tp.paint(canvas, textCenter);

最后,我们就可以将自定义的 slider thumb 应用到 SliderTheme 上了:

代码语言:javascript
复制
SliderTheme(
  data: SliderTheme.of(context).copyWith(
    thumbShape: PolygonSliderThumb(
      thumbRadius: 16.0,
      sliderValue: _value,
    ),
  ),
  child: Slider(...)
)

我们不会介绍其他 slider 组件的构建步骤,但是我们可以使用构建多边形 slider thumb 的这些概念,去创建属于自己风格的 slider

总结

本文覆盖了我们所需要掌握的 slider 挂件的概念。现在,是时候使用 Flutter 创建属于自己独一无二的 slider 了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始
  • RangeSlider 挂件
  • 自定义 slider 颜色
  • 显示 slider 的分割线和标签
  • 展示 slider 的状态
  • 给 sliders 应用主题
  • 通过 CustomPainter设计自定义 sliders
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档