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
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 以一个web项目为例,代码是可以移植的首先要导入mail.jar包,然后创建自己的类1:HTMLSender类package com.txq
- AES类时微软MSDN中最常用的加密类,微软官网也有例子,参考链接:https://docs.microsoft.com/zh-cn/dot
- 一、文件上传概述实现Web开发中的文件上传功能,需要两步操作:1、在Web页面中添加上传输入项 <form
- 本文实例讲述了c#中datagridview处理非绑定列的方法。分享给大家供大家参考。具体实现方法如下:using System;using
- 今天在别人的代码基础上实现新需求,看到对于mybatis查询结果的判断不是很正确,如果查询结果为空就会异常,不知道大家有没有这样的疑惑:my
- Web服务器是运行及发布Web应用的容器,只有将开发的Web项目放置到该容器中,才能使网络中的所有用户通过浏览器进行访问。开发Java We
- 棋牌类游戏是目前比较火的游戏之一。今天本文就以实例形式实现洗牌游戏。本文实例所采用的算法是:遍历每个位置上的牌,然后与随机位置上的牌交换。运
- 我们有时候会遇到这样的情况,需要获取某些中文的拼音、中文首字母缩写和中文首字母,下面我将为大家介绍一下如何获取中文拼音的缩写。1、项目建立和
- 微信朋友圈上面的图片封面,QQ空间说说上面的图片封面都有下拉反弹的效果,这些都是使用滚动条实现的。下拉,当松开时候,反弹至原来的位置。下拉时
- 问题起因主要是使用mybatis作为ORM之后,返回的对象为Map,然后对于数据库的datetime,datestamp类型返回为时间戳而不
- 这篇文章主要介绍了Java代码块与代码加载顺序原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 背景在一些业务场景, 往往需要自定义异常来满足特定的业务, 主流用法是在catch里抛出异常, 例如:public void deal()
- 背景 我们知道在.NET Framework中存在四种常用的定时器,他们分别是:1 两个是通用的多线程定时器:Syste
- 写在前面,首先,我用的java转json序列化的工具是java开源的jackson。今天在做后端接口的时候,发现获取的json对象中少了几个
- Eclipse 最佳字体 推荐:步骤:Eclipse->Windows[窗口]->Preferences[首选项]->Ge
- C#限速下载网络文件的方法,具体如下:using System;using System.Collections.Concurrent;us
- 前言:我们每天都在编写Java代码,编译,执行。很多人已经知道Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.cl
- 本文实例为大家分享了android实现点击图片全屏展示的具体代码,供大家参考,具体内容如下MainActivity:public class
- 前几天网上突然出现流言:某东发生数据泄露12G,最终某东在一篇声明中没有否认,还算是勉强承认了吧,这件事对于一般人有什么影响、应该怎么做已经
- C++编写的一个图书管理系统,供大家参考,具体内容如下2018大一的课设,搬到这纪念一下,共1200多行代码为图书管理人员编写一个图书管理系