flutter轮子计划之进度条

作者:卡路里我的天敌 时间:2023-06-21 07:59:39 

前言

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

  • 进度的范围为0到1的double类型数据

  • 支持拖动,通过回调函数获取进度值

  • 支持跳转,点击某个位置后进度跳转,回调进度值

  • 样式为Material风格的样式,可以根据需要修改

识别拖动手势

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


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()更新界面。上面


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控件,其定义如下:


class VideoProgressBarController extends ChangeNotifier
{
 double progressBarValue = .0;

updateProgressValue(double value){
    progressBarValue = value;
   notifyListeners();
 }
}

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

使用CustomPaint绘制进度条

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


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,
   );
 }
}

完整代码:


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;
}

来源:https://blog.csdn.net/weixin_44239910/article/details/120660336

标签:flutter,进度条
0
投稿

猜你喜欢

  • java 值Document解析xml详细介绍

    2021-11-13 15:59:00
  • java多线程CountDownLatch与线程池ThreadPoolExecutor/ExecutorService案例

    2021-06-21 12:29:50
  • java8 实现提取集合对象的每个属性

    2023-10-17 19:37:27
  • java编程常用技术(推荐)

    2023-11-06 09:03:44
  • java 三种将list转换为map的方法详解

    2023-09-13 03:35:39
  • SpringCloud Zuul过滤器和谷歌Gauva实现限流

    2022-08-31 11:59:21
  • java调用回调机制详解

    2023-11-14 21:53:21
  • springboot中bean的加载顺序问题

    2022-01-04 19:55:57
  • Android实现自动匹配关键字并且标红功能

    2023-05-29 06:19:28
  • Android Selector和Shape的使用方法

    2022-08-05 23:48:38
  • Android N获取外置SD卡或挂载U盘路径的方法

    2023-07-14 22:21:03
  • Java 实战项目锤炼之网上花店商城的实现流程

    2021-09-14 04:51:45
  • Unity代码实现序列帧动画播放器

    2023-03-24 23:48:45
  • 浅谈HashMap、HashTable的key和value是否可为null

    2022-03-12 19:57:47
  • android指定DatePickerDialog样式并不显示年的实现代码

    2022-07-13 05:23:08
  • java动态口令登录实现过程详解

    2022-01-01 10:16:28
  • C#基于QRCode实现动态生成自定义二维码图片功能示例

    2023-04-03 04:08:43
  • java代理 jdk动态代理应用案列

    2023-02-17 10:08:55
  • Android编程之ProgressBar圆形进度条颜色设置方法

    2022-03-11 15:26:25
  • C#读写INI文件的方法

    2023-12-08 15:31:10
  • asp之家 软件编程 m.aspxhome.com