# 实例:圆形渐变进度条(自绘)
本节我们实现一个圆形渐变进度条,它支持:
1. 支持多种渐变色。
2. 任意弧度;整个进度可以不是整圆。
3. 可以自定义粗细、两端是否圆角等样式。
可以发现要实现这样的一个进度条是无法通过现有组件组合而成的,所以我们通过自绘方式实现。
实现代码如下:
```
import 'dart:math';
import 'package:flutter/material.dart';
class GradientCircularProgressIndicator extends StatelessWidget {
GradientCircularProgressIndicator({
this.stokeWidth = 2.0,
@required this.radius,
@required this.colors,
this.stops,
this.strokeCapRound = false,
this.backgroundColor = const Color(0xFFEEEEEE),
this.totalAngle = 2 * pi,
this.value
});
///粗细
final double stokeWidth;
/// 圆的半径
final double radius;
///两端是否为圆角
final bool strokeCapRound;
/// 当前进度,取值范围 [0.0-1.0]
final double value;
/// 进度条背景色
final Color backgroundColor;
/// 进度条的总弧度,2*PI为整圆,小于2*PI则不是整圆
final double totalAngle;
/// 渐变色数组
final List<Color> colors;
/// 渐变色的终止点,对应colors属性
final List<double> stops;
@override
Widget build(BuildContext context) {
double _offset = .0;
// 如果两端为圆角,则需要对起始位置进行调整,否则圆角部分会偏离起始位置
// 下面调整的角度的计算公式是通过数学几何知识得出,读者有兴趣可以研究一下为什么是这样
if (strokeCapRound) {
_offset = asin(stokeWidth / (radius * 2 - stokeWidth));
}
var _colors = colors;
if (_colors == null) {
Color color = Theme
.of(context)
.accentColor;
_colors = [color, color];
}
return Transform.rotate(
angle: -pi / 2.0 - _offset,
child: CustomPaint(
size: Size.fromRadius(radius),
painter: _GradientCircularProgressPainter(
stokeWidth: stokeWidth,
strokeCapRound: strokeCapRound,
backgroundColor: backgroundColor,
value: value,
total: totalAngle,
radius: radius,
colors: _colors,
)
),
);
}
}
//实现画笔
class _GradientCircularProgressPainter extends CustomPainter {
_GradientCircularProgressPainter({
this.stokeWidth: 10.0,
this.strokeCapRound: false,
this.backgroundColor = const Color(0xFFEEEEEE),
this.radius,
this.total = 2 * pi,
@required this.colors,
this.stops,
this.value
});
final double stokeWidth;
final bool strokeCapRound;
final double value;
final Color backgroundColor;
final List<Color> colors;
final double total;
final double radius;
final List<double> stops;
@override
void paint(Canvas canvas, Size size) {
if (radius != null) {
size = Size.fromRadius(radius);
}
double _offset = stokeWidth / 2.0;
double _value = (value ?? .0);
_value = _value.clamp(.0, 1.0) * total;
double _start = .0;
if (strokeCapRound) {
_start = asin(stokeWidth/ (size.width - stokeWidth));
}
Rect rect = Offset(_offset, _offset) & Size(
size.width - stokeWidth,
size.height - stokeWidth
);
var paint = Paint()
..strokeCap = strokeCapRound ? StrokeCap.round : StrokeCap.butt
..style = PaintingStyle.stroke
..isAntiAlias = true
..strokeWidth = stokeWidth;
// 先画背景
if (backgroundColor != Colors.transparent) {
paint.color = backgroundColor;
canvas.drawArc(
rect,
_start,
total,
false,
paint
);
}
// 再画前景,应用渐变
if (_value > 0) {
paint.shader = SweepGradient(
startAngle: 0.0,
endAngle: _value,
colors: colors,
stops: stops,
).createShader(rect);
canvas.drawArc(
rect,
_start,
_value,
false,
paint
);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
```
下面我们来测试一下,为了尽可能多的展示GradientCircularProgressIndicator的不同外观和用途,这个示例代码会比较长,并且添加了动画,建议读者将此示例运行起来观看实际效果,我们先看看其中的一帧动画的截图:
![](https://box.kancloud.cn/4cea87cbe9c575e51882195216b7afb9_326x502.png)
完整的代码:
```
import 'dart:math';
import 'package:flutter/material.dart';
import '../widgets/index.dart';
class GradientCircularProgressRoute extends StatefulWidget {
@override
GradientCircularProgressRouteState createState() {
return new GradientCircularProgressRouteState();
}
}
class GradientCircularProgressRouteState
extends State<GradientCircularProgressRoute> with TickerProviderStateMixin {
AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 3));
bool isForward = true;
_animationController.addStatusListener((status) {
if (status == AnimationStatus.forward) {
isForward = true;
} else if (status == AnimationStatus.completed ||
status == AnimationStatus.dismissed) {
if (isForward) {
_animationController.reverse();
} else {
_animationController.forward();
}
} else if (status == AnimationStatus.reverse) {
isForward = false;
}
});
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget child) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
children: <Widget>[
Wrap(
spacing: 10.0,
runSpacing: 16.0,
children: <Widget>[
GradientCircularProgressIndicator(
// No gradient
colors: [Colors.blue, Colors.blue],
radius: 50.0,
stokeWidth: 3.0,
value: _animationController.value,
),
GradientCircularProgressIndicator(
colors: [Colors.red, Colors.orange],
radius: 50.0,
stokeWidth: 3.0,
value: _animationController.value,
),
GradientCircularProgressIndicator(
colors: [Colors.red, Colors.orange, Colors.red],
radius: 50.0,
stokeWidth: 5.0,
value: _animationController.value,
),
GradientCircularProgressIndicator(
colors: [Colors.teal, Colors.cyan],
radius: 50.0,
stokeWidth: 5.0,
strokeCapRound: true,
value: CurvedAnimation(
parent: _animationController,
curve: Curves.decelerate)
.value,
),
TurnBox(
turns: 1 / 8,
child: GradientCircularProgressIndicator(
colors: [Colors.red, Colors.orange, Colors.red],
radius: 50.0,
stokeWidth: 5.0,
strokeCapRound: true,
backgroundColor: Colors.red[50],
totalAngle: 1.5 * pi,
value: CurvedAnimation(
parent: _animationController,
curve: Curves.ease)
.value),
),
RotatedBox(
quarterTurns: 1,
child: GradientCircularProgressIndicator(
colors: [Colors.blue[700], Colors.blue[200]],
radius: 50.0,
stokeWidth: 3.0,
strokeCapRound: true,
backgroundColor: Colors.transparent,
value: _animationController.value),
),
GradientCircularProgressIndicator(
colors: [
Colors.red,
Colors.amber,
Colors.cyan,
Colors.green[200],
Colors.blue,
Colors.red
],
radius: 50.0,
stokeWidth: 5.0,
strokeCapRound: true,
value: _animationController.value,
),
],
),
GradientCircularProgressIndicator(
colors: [Colors.blue[700], Colors.blue[200]],
radius: 100.0,
stokeWidth: 20.0,
value: _animationController.value,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: GradientCircularProgressIndicator(
colors: [Colors.blue[700], Colors.blue[300]],
radius: 100.0,
stokeWidth: 20.0,
value: _animationController.value,
strokeCapRound: true,
),
),
//剪裁半圆
ClipRect(
child: Align(
alignment: Alignment.topCenter,
heightFactor: .5,
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(
//width: 100.0,
child: TurnBox(
turns: .75,
child: GradientCircularProgressIndicator(
colors: [Colors.teal, Colors.cyan[500]],
radius: 100.0,
stokeWidth: 8.0,
value: _animationController.value,
totalAngle: pi,
strokeCapRound: true,
),
),
),
),
),
),
SizedBox(
height: 104.0,
width: 200.0,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
height: 200.0,
top: .0,
child: TurnBox(
turns: .75,
child: GradientCircularProgressIndicator(
colors: [Colors.teal, Colors.cyan[500]],
radius: 100.0,
stokeWidth: 8.0,
value: _animationController.value,
totalAngle: pi,
strokeCapRound: true,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text(
"${(_animationController.value * 100).toInt()}%",
style: TextStyle(
fontSize: 25.0,
color: Colors.blueGrey,
),
),
)
],
),
),
],
),
);
},
),
],
),
),
);
}
}
```
- 缘起
- 起步
- 移动开发技术简介
- Flutter简介
- 搭建Flutter开发环境
- 常见配置问题
- Dart语言简介
- 第一个Flutter应用
- 计数器示例
- 路由管理
- 包管理
- 资源管理
- 调试Flutter APP
- Dart线程模型及异常捕获
- 基础Widgets
- Widget简介
- 文本、字体样式
- 按钮
- 图片和Icon
- 单选框和复选框
- 输入框和表单
- 布局类Widgets
- 布局类Widgets简介
- 线性布局Row、Column
- 弹性布局Flex
- 流式布局Wrap、Flow
- 层叠布局Stack、Positioned
- 容器类Widgets
- Padding
- 布局限制类容器ConstrainedBox、SizeBox
- 装饰容器DecoratedBox
- 变换Transform
- Container容器
- Scaffold、TabBar、底部导航
- 可滚动Widgets
- 可滚动Widgets简介
- SingleChildScrollView
- ListView
- GridView
- CustomScrollView
- 滚动监听及控制ScrollController
- 功能型Widgets
- 导航返回拦截-WillPopScope
- 数据共享-InheritedWidget
- 主题-Theme
- 事件处理与通知
- 原始指针事件处理
- 手势识别
- 全局事件总线
- 通知Notification
- 动画
- Flutter动画简介
- 动画结构
- 自定义路由过渡动画
- Hero动画
- 交错动画
- 自定义Widget
- 自定义Widget方法简介
- 通过组合现有Widget实现
- 实例:TurnBox
- CustomPaint与Canvas
- 实例:圆形渐变进度条(自绘)
- 文件操作与网络请求
- 文件操作
- Http请求-HttpClient
- Http请求-Dio package
- 实例:Http分块下载
- WebSocket
- 使用Socket API
- Json转Model
- 包与插件
- 开发package
- 插件开发:平台通道简介
- 插件开发:实现Android端API
- 插件开发:实现IOS端API
- 系统能力调用
- 国际化
- 让App支持多语言
- 实现Localizations
- 使用Intl包
- Flutter核心原理
- Flutter UI系统
- Element和BuildContext
- RenderObject与RenderBox
- Flutter从启动到显示