今天寫個簡單的,自定義一個圓形進度條,並且加上小箭頭指向內圈進度。
進度條已上傳到公網,使用circle_progress: ^0.0.1
,使用如下
void main() => runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("Flutter 之旅"),
),
body: TestStateful() //內建案例
)
));
複製程式碼
1.準備階段
1.1:定義描述物件類Progress
將需要變化的屬性抽離出一個描述類,傳參方便些
///資訊描述類 [value]為進度,在0~1之間,進度條顏色[color],
///未完成的顏色[backgroundColor],圓的半徑[radius],線寬[strokeWidth]
///小點的個數[dotCount] 樣式[style] 完成後的顯示文字[completeText]
class Progress {
double value;
Color color;
Color backgroundColor;
double radius;
double strokeWidth;
int dotCount;
TextStyle style;
String completeText;
Progress({this.value,
this.color,
this.backgroundColor,
this.radius,
this.strokeWidth,
this.completeText="OK",
this.style,
this.dotCount = 40
});
}
複製程式碼
1.2:自定義元件類CircleProgressWidget
這便是我們的元件
class CircleProgressWidget extends StatefulWidget {
final Progress progress;
CircleProgressWidget({Key key, this.progress}) : super(key: key);
@override
_CircleProgressWidgetState createState() => _CircleProgressWidgetState();
}
class _CircleProgressWidgetState extends State<CircleProgressWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
複製程式碼
1.3:自定義ProgressPainter
我們的繪製邏輯在這裡進行
class ProgressPainter extends CustomPainter {
Progress _progress;
Paint _paint;
Paint _arrowPaint;//箭頭的畫筆
Path _arrowPath;//箭頭的路徑
double _radius;//半徑
ProgressPainter(
this._progress,
) {
_arrowPath=Path();
_arrowPaint=Paint();
_paint = Paint();
_radius = _progress.radius - _progress.strokeWidth / 2;
}
@override
void paint(Canvas canvas, Size size) {
Rect rect = Offset.zero & size;
canvas.clipRect(rect); //裁剪區域
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
複製程式碼
2.繪製
2.1:繪製進度條
如果直接用給定的半徑,你會發現是這樣的。原因很簡單,因為Canvas畫圓半徑是內圓加一半線粗。
於是我們需要校正一下半徑:通過平移一半線粗再縮小一半線粗的半徑。
_radius = _progress.radius - _progress.strokeWidth / 2;
@override
void paint(Canvas canvas, Size size) {
canvas.translate(_progress.strokeWidth / 2, _progress.strokeWidth / 2);
複製程式碼
背景直接畫圓,進度使用drawArc方法,要注意的是Flutter中使用的是
弧度
!!。
drawProgress(Canvas canvas) {
canvas.save();
_paint//背景
..style = PaintingStyle.stroke
..color = _progress.backgroundColor
..strokeWidth = _progress.strokeWidth;
canvas.drawCircle(Offset(_radius, _radius), _radius, _paint);
_paint//進度
..color = _progress.color
..strokeWidth = _progress.strokeWidth * 1.2
..strokeCap = StrokeCap.round;
double sweepAngle = _progress.value * 360; //完成角度
canvas.drawArc(Rect.fromLTRB(0, 0, _radius * 2, _radius * 2),
-90 / 180 * pi, sweepAngle / 180 * pi, false, _paint);
canvas.restore();
}
複製程式碼
2.2:繪製箭頭
其實箭頭還是蠻好畫的,注意relativeLineTo和lineTo結合使用,可能會更方便。
drawArrow(Canvas canvas) {
canvas.save();
canvas.translate(_radius, _radius);
canvas.rotate((180 + _progress.value * 360) / 180 * pi);
var half = _radius / 2;
var eg = _radius / 50; //單位長
_arrowPath.moveTo(0, -half - eg * 2);//1
_arrowPath.relativeLineTo(eg * 2, eg * 6);//2
_arrowPath.lineTo(0, -half + eg * 2);//3
_arrowPath.lineTo(0, -half - eg * 2);//1
_arrowPath.relativeLineTo(-eg * 2, eg * 6);
_arrowPath.lineTo(0, -half + eg * 2);
_arrowPath.lineTo(0, -half - eg * 2);
canvas.drawPath(_arrowPath, _arrowPaint);
canvas.restore();
}
複製程式碼
2.3:繪製點
繪製點的時候要注意顏色的把控,判斷進度條是否到達,然後更改顏色
void drawDot(Canvas canvas) {
canvas.save();
int num = _progress.dotCount;
canvas.translate(_radius, _radius);
for (double i = 0; i < num; i++) {
canvas.save();
double deg = 360 / num * i;
canvas.rotate(deg / 180 * pi);
_paint
..strokeWidth = _progress.strokeWidth / 2
..color = _progress.backgroundColor
..strokeCap = StrokeCap.round;
if (i * (360 / num) <= _progress.value * 360) {
_paint..color = _progress.color;
}
canvas.drawLine(
Offset(0, _radius * 3 / 4), Offset(0, _radius * 4 / 5), _paint);
canvas.restore();
}
canvas.restore();
}
複製程式碼
2.4:拼裝
也許你會問Text呢?在Canvas裡畫Text好麻煩,這裡用一個Stack包一下挺方便的
class _CircleProgressWidgetState extends State<CircleProgressWidget> {
@override
Widget build(BuildContext context) {
var progress = Container(
width: widget.progress.radius * 2,
height: widget.progress.radius * 2,
child: CustomPaint(
painter: ProgressPainter(widget.progress),
),
);
String txt = "${(100 * widget.progress.value).toStringAsFixed(1)} %";
var text = Text(
widget.progress.value == 1.0 ? widget.progress.completeText : txt,
style: widget.progress.style ??
TextStyle(fontSize: widget.progress.radius / 6),
);
return Stack(
alignment: Alignment.center,
children: <Widget>[progress,text],
);
}
}
複製程式碼
OK,這樣就可以了。
3.使用
3.1:簡單使用
CircleProgressWidget(
progress: Progress(
backgroundColor: Colors.grey,
value: 0.8,
radius: 100,
completeText: "完成",
color: Color(0xff46bcf6),
strokeWidth: 4))
複製程式碼
3.2:元件程式碼全覽
拿去用吧,謝謝,不送。
import 'dart:math';
import 'package:flutter/material.dart';
class CircleProgressWidget extends StatefulWidget {
final Progress progress;
CircleProgressWidget({Key key, this.progress}) : super(key: key);
@override
_CircleProgressWidgetState createState() => _CircleProgressWidgetState();
}
///資訊描述類 [value]為進度,在0~1之間,進度條顏色[color],
///未完成的顏色[backgroundColor],圓的半徑[radius],線寬[strokeWidth]
///小點的個數[dotCount] 樣式[style] 完成後的顯示文字[completeText]
class Progress {
double value;
Color color;
Color backgroundColor;
double radius;
double strokeWidth;
int dotCount;
TextStyle style;
String completeText;
Progress(
{this.value,
this.color,
this.backgroundColor,
this.radius,
this.strokeWidth,
this.completeText = "OK",
this.style,
this.dotCount = 40});
}
class _CircleProgressWidgetState extends State<CircleProgressWidget> {
@override
Widget build(BuildContext context) {
var progress = Container(
width: widget.progress.radius * 2,
height: widget.progress.radius * 2,
child: CustomPaint(
painter: ProgressPainter(widget.progress),
),
);
String txt = "${(100 * widget.progress.value).toStringAsFixed(1)} %";
var text = Text(
widget.progress.value == 1.0 ? widget.progress.completeText : txt,
style: widget.progress.style ??
TextStyle(fontSize: widget.progress.radius / 6),
);
return Stack(
alignment: Alignment.center,
children: <Widget>[progress,text],
);
}
}
class ProgressPainter extends CustomPainter {
Progress _progress;
Paint _paint;
Paint _arrowPaint;
Path _arrowPath;
double _radius;
ProgressPainter(
this._progress,
) {
_arrowPath = Path();
_arrowPaint = Paint();
_paint = Paint();
_radius = _progress.radius - _progress.strokeWidth / 2;
}
@override
void paint(Canvas canvas, Size size) {
Rect rect = Offset.zero & size;
canvas.clipRect(rect); //裁剪區域
canvas.translate(_progress.strokeWidth / 2, _progress.strokeWidth / 2);
drawProgress(canvas);
drawArrow(canvas);
drawDot(canvas);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
drawProgress(Canvas canvas) {
canvas.save();
_paint//背景
..style = PaintingStyle.stroke
..color = _progress.backgroundColor
..strokeWidth = _progress.strokeWidth;
canvas.drawCircle(Offset(_radius, _radius), _radius, _paint);
_paint//進度
..color = _progress.color
..strokeWidth = _progress.strokeWidth * 1.2
..strokeCap = StrokeCap.round;
double sweepAngle = _progress.value * 360; //完成角度
print(sweepAngle);
canvas.drawArc(Rect.fromLTRB(0, 0, _radius * 2, _radius * 2),
-90 / 180 * pi, sweepAngle / 180 * pi, false, _paint);
canvas.restore();
}
drawArrow(Canvas canvas) {
canvas.save();
canvas.translate(_radius, _radius);// 將畫板移到中心
canvas.rotate((180 + _progress.value * 360) / 180 * pi);//旋轉相應角度
var half = _radius / 2;//基點
var eg = _radius / 50; //單位長
_arrowPath.moveTo(0, -half - eg * 2);
_arrowPath.relativeLineTo(eg * 2, eg * 6);
_arrowPath.lineTo(0, -half + eg * 2);
_arrowPath.lineTo(0, -half - eg * 2);
_arrowPath.relativeLineTo(-eg * 2, eg * 6);
_arrowPath.lineTo(0, -half + eg * 2);
_arrowPath.lineTo(0, -half - eg * 2);
canvas.drawPath(_arrowPath, _arrowPaint);
canvas.restore();
}
void drawDot(Canvas canvas) {
canvas.save();
int num = _progress.dotCount;
canvas.translate(_radius, _radius);
for (double i = 0; i < num; i++) {
canvas.save();
double deg = 360 / num * i;
canvas.rotate(deg / 180 * pi);
_paint
..strokeWidth = _progress.strokeWidth / 2
..color = _progress.backgroundColor
..strokeCap = StrokeCap.round;
if (i * (360 / num) <= _progress.value * 360) {
_paint..color = _progress.color;
}
canvas.drawLine(
Offset(0, _radius * 3 / 4), Offset(0, _radius * 4 / 5), _paint);
canvas.restore();
}
canvas.restore();
}
}
複製程式碼
結語
本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328
,期待與你的交流與切磋。