今日无事,闲来看了看别人的一些demo,然后自己琢磨着写了一个CircleProgressBar组件。CircleProgressBar组件是我们经常使用的一个控件,一般包括这几个重要的属性:min, max, progress, 构成则是由Arc路径成像。但是在flutter中使用Arc绘制图形会出现一些显示问题而根本达不到你想要的效果。为此下面的代码中我们使用了一点数学小知识。
首先,编写CircleProgressBar
class CircleProgressBar {
CircleProgressBar(this.progress, {this.min = 0.0, this.max = 100.0});
double progress;
double min;
double max;
static CircleProgressBar lerp(
CircleProgressBar begin, CircleProgressBar end, double t) {
return new CircleProgressBar(lerpDouble(begin.progress, end.progress, t));
}
}
接着,编写CircleProgressBar的Tween动画
因为是补间动画,所以会有begin, end两个参数,而lerp函数则是相应动画的线性插件函数,相当于Android原生中的Interpolator。
class CircleProgressBarTween extends Tween<CircleProgressBar> {
CircleProgressBarTween(CircleProgressBar begin, CircleProgressBar end)
: super(begin: begin, end: end);
@override
CircleProgressBar lerp(double t) => CircleProgressBar.lerp(begin, end, t);
}
最后,编写CircleProgressBar自定义UI组件。
构建自定义UI需要继承CustomPainter方法,并重写paint方法和shouldRepaint方法。重写paint方法,使用笔刷和画布绘制控件UI。
class CircleProgressBarPainter extends CustomPainter {
CircleProgressBarPainter(Animation<CircleProgressBar> animation)
: animation = animation,
super(repaint: animation);
Animation<CircleProgressBar> animation;
@override
void paint(Canvas canvas, Size size) {
final progressBar = animation.value;
final paint = new Paint()
..color = Colors.grey[400]
..style = PaintingStyle.stroke
..strokeWidth = 8.0;
final double wh = max(size.width, size.height);
final centerX = (size.width - wh) / 2.0,
centerY = (size.height - wh) / 2.0;
final radius = wh / 2.0;
canvas.drawPath(getArcPath(centerX, centerY, radius, true), paint);
paint.color = Colors.red;
canvas.drawPath(getArcPath(centerX, centerY, radius, false, (progressBar.progress / progressBar.max) * 360), paint);
}
///
/// 获取Arc的Path
/// <pre>
/// centerX 圆心坐标x
/// centerY 圆心坐标Y
/// isClosePath 是否闭合路径
/// arcAngle 角度,默认为360°
///
/// 说明:下面代码中使用了一个数学公式
/// 已知圆心坐标(x, y),半径(r),和角度(a),求圆上某个点的坐标,则有:
/// x1 = x + r * cos(a * π / 180.0)
/// y1 = y + r * sin(a * π / 180.0)
/// </pre>
///
Path getArcPath(
double centerX, double centerY, double radius, bool isClosePath, [double arcAngle = 360.0]) {
var path = Path();
for (double i = 0.0; i < arcAngle; i += 0.1) {
double x = centerX + radius * cos(i * 3.141592653 / 180);
double y = centerY / 2.0 + radius * sin(i * 3.141592653 / 180);
if (i != 0)
path.lineTo(x, y);
else
path.moveTo(x, y);
}
if (isClosePath) path.close();
return path;
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
最后上测试代码
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
final random = new Random();
var progress = 0.0;
AnimationController animationController;
CircleProgressBarTween progressBarTween;
@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 300));
progressBarTween = new CircleProgressBarTween(
new CircleProgressBar(0.0, max: 360.0), new CircleProgressBar(0.0, max: 360.0));
animationController.forward();
}
void _incrementCounter() {
setState(() {
progress = random.nextDouble() * 360.0;
animationController = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 300));
progressBarTween = new CircleProgressBarTween(
progressBarTween.evaluate(animationController),
new CircleProgressBar(progress, max: 360.0));
animationController.forward();
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new CustomPaint(
size: const Size(100.0, 100.0),
painter: new CircleProgressBarPainter(
progressBarTween.animate(animationController)),
),
new Text(
'进度条值为: $progress',
),
],
),
),
floatingActionButton: new AnimatedContainer(
duration: const Duration(seconds: 2),
transform: Matrix4.translationValues(0.5, 0.5, 0.5),
child: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
),
);
}
}