我是靠谱客的博主 俭朴树叶,这篇文章主要介绍flutter轮子计划之进度条,现在分享给大家,希望可以做个参考。

前言

本文的记录如何用CustomPaint、GestureDetector实现一个进度条控件。首先需要说明的是 flutter Material 组件库中提供了两种进度指示器:LinearProgressIndicator和CircularProgressIndicator。如果这两种进度指示器可以满足开发需求,就不要尝试自己造轮子了。本文实现的进度条控件,功能如下:

  • 进度的范围为0到1的double类型数据
  • 支持拖动,通过回调函数获取进度值
  • 支持跳转,点击某个位置后进度跳转,回调进度值
  • 样式为Material风格的样式,可以根据需要修改

识别拖动手势

使用GestureDetector可以方便得对滑动,点击事件进行监听。如下是监听的四个事件,重点关注onHorizontalDragUpdate即可,其回调函数将水平拖动事件的坐标等信息传递给_seekToRelativePosition函数。_seekToRelativePosition函数的功能是计算滑动时进度条的值,并更新界面。代码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GestureDetector( onHorizontalDragStart: (DragStartDetails details) { widget.onDragStart?.call(); }, onHorizontalDragUpdate: (DragUpdateDetails details) { widget.onDragUpdate?.call(); _seekToRelativePosition(details.globalPosition); }, onHorizontalDragEnd: (DragEndDetails details) { widget.onDragEnd?.call(progress); }, onTapDown: (TapDownDetails details) { widget.onTapDown?.call(progress); _seekToRelativePosition(details.globalPosition); }, // .... )

_seekToRelativePosition 将全局坐标转换为进度条控件所在的举动坐标。将点击处的横坐标,即x与进度条控件的长度的比率作为进度条的值。然后调用setState()更新界面。上面

复制代码
1
2
3
4
5
6
7
8
9
10
11
void _seekToRelativePosition(Offset globalPosition) { final box = context.findRenderObject()! as RenderBox; final Offset tapPos = box.globalToLocal(globalPosition); progress = tapPos.dx / box.size.width; if (progress < 0) progress = 0; if (progress > 1) progress = 1; setState(() { widget.controller.progressBarValue = progress; }); }

上面代码中有一个controller控件,其定义如下:

复制代码
1
2
3
4
5
6
7
8
9
class VideoProgressBarController extends ChangeNotifier { double progressBarValue = .0; updateProgressValue(double value){ progressBarValue = value; notifyListeners(); } }

其继承自ChangeNotifier, 因为此进度条控件的状态由其他控件和控件本身混合管理状态。当其他控件想改变进度条的值时,可以通过VidoeProgressBarController通知进度条控件更新界面。当然,将进度条控件改用statelesswidget实现,然后直接调用setState()更新界面实现起来会更简单一点,读者有需要可以尝试。

使用CustomPaint绘制进度条

绘制部分比较简单。如下,先绘制灰色背景,然后绘制红色的进度,再回事圆点。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class _VideoProgressBarPainter extends CustomPainter { _VideoProgressBarPainter( {required this.barHeight, required this.handleHeight, required this.value, required this.colors}); final double barHeight; final double handleHeight; final ProgressColors colors; final double value; @override bool shouldRepaint(CustomPainter painter) { return true; } @override void paint(Canvas canvas, Size size) { final baseOffset = size.height / 2 - barHeight / 2; final double radius = 4.0; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( Offset(0.0, baseOffset), Offset(size.width, baseOffset + barHeight), ), const Radius.circular(4.0), ), colors.backgroundPaint, ); double playedPart = value > 1 ? size.width - radius : value * size.width - radius; if (playedPart < radius) { playedPart = radius; } canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( Offset(0.0, baseOffset), Offset(playedPart, baseOffset + barHeight), ), Radius.circular(radius), ), colors.playedPaint, ); canvas.drawCircle( Offset(playedPart, baseOffset + barHeight / 2), handleHeight, colors.playedPaint, ); } }

完整代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { double _progressValue = .5; late VideoProgressBarController controller; @override void initState() { controller = VideoProgressBarController(); super.initState(); } @override Widget build(BuildContext context) { print("build:$_progressValue"); return SafeArea( child: Scaffold( appBar: AppBar(title: Text("test")), body: Column( //aspectRatio: 16 / 9, children: [ Container( width: 200, height: 26, //color: Colors.blue, child: VideoProgressBar( controller: controller, barHeight: 2, onDragEnd: (double progress) { print("$progress"); }, ), ), Text("value:$_progressValue"), ElevatedButton( onPressed: (){ _progressValue = 1; controller.updateProgressValue(_progressValue); }, child: Text("increase") ) ] ), ), ); } } /// progress bar class VideoProgressBar extends StatefulWidget { VideoProgressBar({ ProgressColors? colors, Key? key, required this.controller, required this.barHeight, this.handleHeight = 6, this.onDragStart, this.onDragEnd, this.onDragUpdate, this.onTapDown, }) : colors = colors ?? ProgressColors(), super(key: key); final ProgressColors colors; final Function()? onDragStart; final Function(double progress)? onDragEnd; final Function()? onDragUpdate; final Function(double progress)? onTapDown; final double barHeight; final double handleHeight; final TVideoProgressBarController controller; //final bool drawShadow; @override _VideoProgressBarState createState() => _VideoProgressBarState(); } class _VideoProgressBarState extends State<VideoProgressBar> { double progress = .0; @override void initState() { super.initState(); progress = widget.controller.progressBarValue; widget.controller.addListener(_updateProgressValue); } @override void dispose() { widget.controller.removeListener(_updateProgressValue); super.dispose(); } _updateProgressValue() { setState(() { progress = widget.controller.progressBarValue; }); } void _seekToRelativePosition(Offset globalPosition) { final box = context.findRenderObject()! as RenderBox; final Offset tapPos = box.globalToLocal(globalPosition); progress = tapPos.dx / box.size.width; if (progress < 0) progress = 0; if (progress > 1) progress = 1; setState(() { widget.controller.progressBarValue = progress; }); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; return GestureDetector( onHorizontalDragStart: (DragStartDetails details) { widget.onDragStart?.call(); }, onHorizontalDragUpdate: (DragUpdateDetails details) { widget.onDragUpdate?.call(); _seekToRelativePosition(details.globalPosition); }, onHorizontalDragEnd: (DragEndDetails details) { widget.onDragEnd?.call(progress); }, onTapDown: (TapDownDetails details) { widget.onTapDown?.call(progress); _seekToRelativePosition(details.globalPosition); }, child: Center( child: Container( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, child: CustomPaint( painter: _VideoProgressBarPainter( barHeight: widget.barHeight, handleHeight: widget.handleHeight, colors: widget.colors, value: progress)), ), )); } } class _VideoProgressBarPainter extends CustomPainter { _VideoProgressBarPainter( {required this.barHeight, required this.handleHeight, required this.value, required this.colors}); final double barHeight; final double handleHeight; final ProgressColors colors; final double value; @override bool shouldRepaint(CustomPainter painter) { return true; } @override void paint(Canvas canvas, Size size) { final baseOffset = size.height / 2 - barHeight / 2; final double radius = 4.0; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( Offset(0.0, baseOffset), Offset(size.width, baseOffset + barHeight), ), const Radius.circular(4.0), ), colors.backgroundPaint, ); double playedPart = value > 1 ? size.width - radius : value * size.width - radius; if (playedPart < radius) { playedPart = radius; } canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( Offset(0.0, baseOffset), Offset(playedPart, baseOffset + barHeight), ), Radius.circular(radius), ), colors.playedPaint, ); canvas.drawCircle( Offset(playedPart, baseOffset + barHeight / 2), handleHeight, colors.playedPaint, ); } } class VideoProgressBarController extends ChangeNotifier { double progressBarValue = .0; updateProgressValue(double value){ progressBarValue = value; notifyListeners(); } } class ProgressColors { ProgressColors({ Color playedColor = const Color.fromRGBO(255, 0, 0, 0.7), Color bufferedColor = const Color.fromRGBO(30, 30, 200, 0.2), Color handleColor = const Color.fromRGBO(200, 200, 200, 1.0), Color backgroundColor = const Color.fromRGBO(200, 200, 200, 0.5), }) : playedPaint = Paint()..color = playedColor, bufferedPaint = Paint()..color = bufferedColor, handlePaint = Paint()..color = handleColor, backgroundPaint = Paint()..color = backgroundColor; final Paint playedPaint; final Paint bufferedPaint; final Paint handlePaint; final Paint backgroundPaint; }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持靠谱客。

最后

以上就是俭朴树叶最近收集整理的关于flutter轮子计划之进度条的全部内容,更多相关flutter轮子计划之进度条内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(143)

评论列表共有 0 条评论

立即
投稿
返回
顶部