-
演示
先看效果图:
由于无法截取动态图,我就截过程中的两张图片表达了,我想应该能看得懂。
- 功能
1.设置进度条的宽高
2.设置进度条的三个进度颜色
3.设置两个seekbar位置
4.分别设置seekbar的颜色
5.设置seekbar的宽高
6.seekbar可实时华东并显示进度值在seekbar上
7.实时回调两个seekbar的进度值
- 代码
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class TwoButtonSeekBar extends StatefulWidget {
/// 第一段进度条颜色
final Color? colorStartProgress;
/// 第二段进度条颜色
final Color? colorCenterProgress;
/// 第三段进度条颜色
final Color? colorEndProgress;
/// 进度条宽度
final double? widthProgress;
/// 进度条高度
final double? heightProgress;
/// 进度条最大值
final double? valueMaxProgress;
/// 第一个seekbar位置
final double? valueOneSeekBar;
/// 第二个seekbar位置
final double? valueTwoSeekBar;
/// 第一个seekbar颜色
final Color? colorOneSeekBar;
/// 第二个seekbar颜色
final Color? colorTwoSeekBar;
/// seekbar宽度
final double? widthSeekBar;
/// seekbar高度
final double? heightSeekBar;
/// seekbar上是否显示进度文本
final bool? isShowProgressText;
/// seekbar上文本颜色
final Color? colorProgressText;
/// seekbar上文本大小
final double? sizeProgressText;
/// 第一个seekbar的值的回调
final Function(double one, double two)? onSeekBarCallback;
const TwoButtonSeekBar(
{super.key,
this.colorStartProgress,
this.colorCenterProgress,
this.colorEndProgress,
this.widthProgress,
this.heightProgress,
this.valueMaxProgress,
this.valueOneSeekBar,
this.valueTwoSeekBar,
this.colorOneSeekBar,
this.colorTwoSeekBar,
this.widthSeekBar,
this.heightSeekBar,
this.isShowProgressText,
this.colorProgressText,
this.sizeProgressText,
this.onSeekBarCallback});
@override
State<StatefulWidget> createState() => _TwoButtonSeekBarState();
}
class _TwoButtonSeekBarState extends State<TwoButtonSeekBar> {
PointerEvent? _event;
/// 按下是否命中目标
bool _isHit = true;
/// 是否点击中了第一个seekbar
bool _isHitOneSeekBar = true;
/// 第一个seekbar的值
double? _seekOneValue;
/// 第二个seekbar的值
double? _seekTwoValue;
/// 减少误差的偏移
double _offset = 0;
@override
void initState() {
super.initState();
_seekOneValue = widget.valueOneSeekBar;
_seekTwoValue = widget.valueTwoSeekBar;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: (_) {},
child: SizedBox(
width: widget.widthProgress ?? 400.w,
height: ((widget.heightProgress ?? 0) > (widget.heightSeekBar ?? 0))
? widget.heightProgress ?? 20.w
: widget.heightSeekBar ?? 20.w,
child: Listener(
child: CustomPaint(
painter: _SeekBarPrinter(
this,
_seekOneValue ?? 100.w,
_seekTwoValue ?? 200.w,
_offset,
_isHitOneSeekBar,
_event, (isHit, isHitOneSeekBar, offset) {
//保存是否点击中seekbar的状态
_isHit = isHit;
//保存是否点中第一个seekbar的状态
_isHitOneSeekBar = isHitOneSeekBar;
//保存点击在seekbar上离中心点的偏移值,方便计算实际移动值
_offset = offset;
}, (one, two) {
//保存第一个seekbar进度
_seekOneValue = one;
//保存第二个seekbar进度
_seekTwoValue = two;
if (widget.onSeekBarCallback != null) {
//回调进度条seekbar的值
widget.onSeekBarCallback!(one, two);
}
}),
),
onPointerDown: (PointerDownEvent event) =>
setState(() => _event = event),
onPointerMove: (PointerMoveEvent event) {
if (_isHit) {
setState(() => _event = event);
}
},
onPointerUp: (PointerUpEvent event) {
if (_isHit) {
setState(() => _event = event);
}
},
)));
}
}
class _SeekBarPrinter extends CustomPainter {
/// state
final _TwoButtonSeekBarState state;
/// 点击事件
final PointerEvent? _event;
/// 点击是否击中都会回调
final Function(bool isHit, bool isHitOneSeekBar, double offset)? _hitCallback;
/// seekbar数据改变时回调
final Function(double seekOneValue, double seekTwovalue)? _onSeekBarCallback;
/// 第一个seekbar的位置值
double _seekOneValue;
/// 第二个seekbar的位置值
double _seekTwoValue;
/// 是否点击中了第一个seekbar
final bool _isHitOneSeekBar;
/// 减少误差的偏移
final double _offset;
_SeekBarPrinter(
this.state,
this._seekOneValue,
this._seekTwoValue,
this._offset,
this._isHitOneSeekBar,
this._event,
this._hitCallback,
this._onSeekBarCallback);
@override
void paint(Canvas canvas, Size size) {
//事件点击的中心位置
Offset? eventOffset = _event?.localPosition;
//事件点击的中心位置移动量
double? eventDx = eventOffset?.dx;
// seekbar的宽度
double widthSeekBar = state.widget.widthSeekBar ?? 40.w;
// seekbar的高度
double heightSeekBar = state.widget.heightSeekBar ?? 20.w;
//判断按下时是否按在seekbar上
if (_event is PointerDownEvent) {
if ((eventDx! - _seekOneValue).abs() <= widthSeekBar / 2) {
//是否按在第一个seekbar上
_hitCallback!(true, true, eventDx - _seekOneValue);
} else if ((eventDx - _seekTwoValue).abs() <= widthSeekBar / 2) {
//是否按在第二个seekbar上
_hitCallback!(true, false, eventDx - _seekTwoValue);
} else {
//未按在seekbar上
_hitCallback!(false, true, 0);
}
}
//是移动和抬起手势时执行seekbar的移动相关动画
if (_event is PointerMoveEvent || _event is PointerUpEvent) {
//当按下时在第一个seekbar上执行以下代码
if (_isHitOneSeekBar) {
//移动偏移量
double moveDx = eventDx! - _seekOneValue - _offset;
//第一个seekbar的位置
_seekOneValue += moveDx;
//范围限定
if (_seekOneValue <= 0) {
//小于最小值
_seekOneValue = 0;
} else if (_seekOneValue >= (state.widget.valueMaxProgress ?? 300.w)) {
//大于最大值
_seekOneValue = state.widget.valueMaxProgress ?? 300.w;
} else if (_seekOneValue >= _seekTwoValue) {
//大于第二个seekbar
_seekTwoValue = _seekOneValue;
}
} else {
//当按下时在第二个seekbar上执行以下代码
//移动偏移量
double moveDx = eventDx! - _seekTwoValue - _offset;
//第二个seekbar的位置
_seekTwoValue += moveDx;
//范围限定
if (_seekTwoValue <= 0) {
//小于最小值
_seekTwoValue = 0;
} else if (_seekTwoValue >= (state.widget.valueMaxProgress ?? 300.w)) {
//大于最大值
_seekTwoValue = state.widget.valueMaxProgress ?? 300.w;
} else if (_seekTwoValue <= _seekOneValue) {
//小于第一个seekbar
_seekOneValue = _seekTwoValue;
}
}
//回调两个seekbar的当前值
_onSeekBarCallback!(_seekOneValue, _seekTwoValue);
}
//进度条的宽高
double heightProgress = state.widget.heightProgress ?? 10.w;
double widthEndProgress = state.widget.widthProgress ?? 300.w;
//第一段进度条的rect
Rect rect1 = Rect.fromCenter(
center: Offset(widthEndProgress / 2, heightProgress),
width: widthEndProgress,
height: heightProgress);
//第二段进度条的rect
Rect rect2 = Rect.fromCenter(
center: Offset(_seekTwoValue / 2, heightProgress),
width: _seekTwoValue,
height: heightProgress);
//第三段进度条的rect
Rect rect3 = Rect.fromCenter(
center: Offset(_seekOneValue / 2, heightProgress),
width: _seekOneValue,
height: heightProgress);
//画笔
final Paint paint = Paint()
..strokeCap = StrokeCap.round
..isAntiAlias = true
..style = PaintingStyle.fill;
paint.color = state.widget.colorEndProgress ?? Colors.blueAccent;
//先画第三段进度条
canvas.drawRRect(
RRect.fromRectAndRadius(rect1, const Radius.circular(100000)), paint);
paint.color = state.widget.colorCenterProgress ?? Colors.grey;
//再画第二段进度条
canvas.drawRRect(
RRect.fromRectAndRadius(rect2, const Radius.circular(100000)), paint);
paint.color = state.widget.colorStartProgress ?? Colors.redAccent;
//最后画第一段进度条
canvas.drawRRect(
RRect.fromRectAndRadius(rect3, const Radius.circular(100000)), paint);
//两个seekbar的rect
Rect rectSeekOne = Rect.fromCenter(
center: Offset(_seekOneValue, heightSeekBar / 2),
width: widthSeekBar,
height: heightSeekBar);
Rect rectSeekTwo = Rect.fromCenter(
center: Offset(_seekTwoValue, heightSeekBar / 2),
width: widthSeekBar,
height: heightSeekBar);
paint.color = state.widget.colorOneSeekBar ?? Colors.greenAccent;
//画第一个seekbar
canvas.drawRRect(
RRect.fromRectAndRadius(rectSeekOne, const Radius.circular(4)), paint);
paint.color = state.widget.colorTwoSeekBar ?? Colors.greenAccent;
//画第二个seekbar
canvas.drawRRect(
RRect.fromRectAndRadius(rectSeekTwo, const Radius.circular(4)), paint);
//测量第一个seekbar上文字的宽度
String oneStr = _seekOneValue.toInt().toString();
TextPainter textPainter = TextPainter(
text: TextSpan(
text: oneStr,
style: TextStyle(
color: Colors.white,
fontSize: state.widget.sizeProgressText ?? 14.sp)),
maxLines: 1,
textDirection: TextDirection.ltr)
..layout(minWidth: 0, maxWidth: double.infinity);
//画第一个seekbar上的文字
ui.ParagraphBuilder paragraphBuilder =
ui.ParagraphBuilder(ui.ParagraphStyle())
..addText(oneStr)
..pushStyle(ui.TextStyle(
color: state.widget.colorProgressText ?? Colors.white,
fontSize: state.widget.sizeProgressText ?? 14.sp));
ui.ParagraphConstraints paragraphConstraints =
ui.ParagraphConstraints(width: size.width);
ui.Paragraph paragraph = paragraphBuilder.build()
..layout(paragraphConstraints);
canvas.drawParagraph(
paragraph, Offset(_seekOneValue - textPainter.width / 2, 0));
//测量第二个seekbar上文字的宽度
String twoStr = _seekTwoValue.toInt().toString();
TextPainter textPainter2 = TextPainter(
text: TextSpan(
text: twoStr,
style: TextStyle(
color: Colors.white,
fontSize: state.widget.sizeProgressText ?? 14.sp)),
maxLines: 1,
textDirection: TextDirection.ltr)
..layout(minWidth: 0, maxWidth: double.infinity);
//画第二个seekbar上的文字
ui.ParagraphBuilder paragraphBuilder2 =
ui.ParagraphBuilder(ui.ParagraphStyle())
..addText(twoStr)
..pushStyle(ui.TextStyle(
color: state.widget.colorProgressText ?? Colors.white,
fontSize: state.widget.sizeProgressText ?? 14.sp));
ui.ParagraphConstraints paragraphConstraints2 =
ui.ParagraphConstraints(width: size.width);
ui.Paragraph paragraph2 = paragraphBuilder2.build()
..layout(paragraphConstraints2);
canvas.drawParagraph(
paragraph2, Offset(_seekTwoValue - textPainter2.width / 2, 0));
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
代码可以直接复制下来使用,注释非常完善,欢迎指正。