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
投稿

猜你喜欢

  • Flutter网络请求的3种简单实现方法

    2023-06-21 10:53:22
  • Flutter实现抽屉动画

    2023-06-18 01:49:19
  • flutter material widget组件之信息展示组件使用详解

    2023-06-22 08:45:35
  • Flutter 容器盒子模型的使用示例

    2023-06-18 18:47:43
  • Android开发组件flutter的20个常用技巧示例总结

    2023-06-19 17:25:23
  • 替换so文件来动态替换Flutter代码实现详解

    2023-06-23 16:24:06
  • 在Flutter中制作翻转卡片动画的完整实例代码

    2023-06-23 23:31:21
  • Flutter路由传递参数及解析实现

    2023-06-22 11:48:45
  • 基于Flutter实现多边形和多角星组件

    2023-06-19 06:02:50
  • 使用flutter创建可移动的stack小部件功能

    2023-06-21 12:28:25
  • 使用Flutter实现一个走马灯布局的示例代码

    2023-06-19 03:50:03
  • Flutter 通过Clipper实现各种自定义形状的示例代码

    2023-06-19 14:25:11
  • flutter ExpansionTile 层级菜单的实现

    2023-06-15 16:04:01
  • Flutter应用集成极光推送的实现示例

    2023-06-24 03:51:04
  • Swift洗牌动画效果的实现方法

    2023-06-21 14:01:56
  • flutter实现appbar下选项卡切换

    2023-06-21 13:35:24
  • Flutter开发中的路由参数处理

    2023-06-21 04:27:48
  • Flutter app页面路由以及路由拦截的实现

    2023-06-23 14:21:18
  • 详解Flutter中视频播放器插件的使用教程

    2023-06-15 23:47:31
  • asp之家 软件编程 m.aspxhome.com