Flutter實戰(zhàn) 自繪實例:圓形背景漸變進度條

2021-03-09 09:21 更新

本節(jié)我們實現(xiàn)一個圓形背景漸變進度條,它支持:

  1. 支持多種背景漸變色。
  2. 任意弧度;進度條可以不是整圓。
  3. 可以自定義粗細、兩端是否圓角等樣式。

可以發(fā)現(xiàn)要實現(xiàn)這樣的一個進度條是無法通過現(xiàn)有組件組合而成的,所以我們通過自繪方式實現(xiàn),代碼如下:

  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. class GradientCircularProgressIndicator extends StatelessWidget {
  4. GradientCircularProgressIndicator({
  5. this.strokeWidth = 2.0,
  6. @required this.radius,
  7. @required this.colors,
  8. this.stops,
  9. this.strokeCapRound = false,
  10. this.backgroundColor = const Color(0xFFEEEEEE),
  11. this.totalAngle = 2 * pi,
  12. this.value
  13. });
  14. ///粗細
  15. final double strokeWidth;
  16. /// 圓的半徑
  17. final double radius;
  18. ///兩端是否為圓角
  19. final bool strokeCapRound;
  20. /// 當前進度,取值范圍 [0.0-1.0]
  21. final double value;
  22. /// 進度條背景色
  23. final Color backgroundColor;
  24. /// 進度條的總弧度,2*PI為整圓,小于2*PI則不是整圓
  25. final double totalAngle;
  26. /// 漸變色數(shù)組
  27. final List<Color> colors;
  28. /// 漸變色的終止點,對應colors屬性
  29. final List<double> stops;
  30. @override
  31. Widget build(BuildContext context) {
  32. double _offset = .0;
  33. // 如果兩端為圓角,則需要對起始位置進行調整,否則圓角部分會偏離起始位置
  34. // 下面調整的角度的計算公式是通過數(shù)學幾何知識得出,讀者有興趣可以研究一下為什么是這樣
  35. if (strokeCapRound) {
  36. _offset = asin(strokeWidth / (radius * 2 - strokeWidth));
  37. }
  38. var _colors = colors;
  39. if (_colors == null) {
  40. Color color = Theme
  41. .of(context)
  42. .accentColor;
  43. _colors = [color, color];
  44. }
  45. return Transform.rotate(
  46. angle: -pi / 2.0 - _offset,
  47. child: CustomPaint(
  48. size: Size.fromRadius(radius),
  49. painter: _GradientCircularProgressPainter(
  50. strokeWidth: strokeWidth,
  51. strokeCapRound: strokeCapRound,
  52. backgroundColor: backgroundColor,
  53. value: value,
  54. total: totalAngle,
  55. radius: radius,
  56. colors: _colors,
  57. )
  58. ),
  59. );
  60. }
  61. }
  62. //實現(xiàn)畫筆
  63. class _GradientCircularProgressPainter extends CustomPainter {
  64. _GradientCircularProgressPainter({
  65. this.strokeWidth: 10.0,
  66. this.strokeCapRound: false,
  67. this.backgroundColor = const Color(0xFFEEEEEE),
  68. this.radius,
  69. this.total = 2 * pi,
  70. @required this.colors,
  71. this.stops,
  72. this.value
  73. });
  74. final double strokeWidth;
  75. final bool strokeCapRound;
  76. final double value;
  77. final Color backgroundColor;
  78. final List<Color> colors;
  79. final double total;
  80. final double radius;
  81. final List<double> stops;
  82. @override
  83. void paint(Canvas canvas, Size size) {
  84. if (radius != null) {
  85. size = Size.fromRadius(radius);
  86. }
  87. double _offset = strokeWidth / 2.0;
  88. double _value = (value ?? .0);
  89. _value = _value.clamp(.0, 1.0) * total;
  90. double _start = .0;
  91. if (strokeCapRound) {
  92. _start = asin(strokeWidth/ (size.width - strokeWidth));
  93. }
  94. Rect rect = Offset(_offset, _offset) & Size(
  95. size.width - strokeWidth,
  96. size.height - strokeWidth
  97. );
  98. var paint = Paint()
  99. ..strokeCap = strokeCapRound ? StrokeCap.round : StrokeCap.butt
  100. ..style = PaintingStyle.stroke
  101. ..isAntiAlias = true
  102. ..strokeWidth = strokeWidth;
  103. // 先畫背景
  104. if (backgroundColor != Colors.transparent) {
  105. paint.color = backgroundColor;
  106. canvas.drawArc(
  107. rect,
  108. _start,
  109. total,
  110. false,
  111. paint
  112. );
  113. }
  114. // 再畫前景,應用漸變
  115. if (_value > 0) {
  116. paint.shader = SweepGradient(
  117. startAngle: 0.0,
  118. endAngle: _value,
  119. colors: colors,
  120. stops: stops,
  121. ).createShader(rect);
  122. canvas.drawArc(
  123. rect,
  124. _start,
  125. _value,
  126. false,
  127. paint
  128. );
  129. }
  130. }
  131. @override
  132. bool shouldRepaint(CustomPainter oldDelegate) => true;
  133. }

下面我們來測試一下,為了盡可能多的展示GradientCircularProgressIndicator的不同外觀和用途,這個示例代碼會比較長,并且添加了動畫,建議讀者將此示例運行起來觀看實際效果,我們先看看其中的一幀動畫的截圖:

gradient_circular_progress

示例代碼:

  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. import '../widgets/index.dart';
  4. class GradientCircularProgressRoute extends StatefulWidget {
  5. @override
  6. GradientCircularProgressRouteState createState() {
  7. return new GradientCircularProgressRouteState();
  8. }
  9. }
  10. class GradientCircularProgressRouteState
  11. extends State<GradientCircularProgressRoute> with TickerProviderStateMixin {
  12. AnimationController _animationController;
  13. @override
  14. void initState() {
  15. super.initState();
  16. _animationController =
  17. new AnimationController(vsync: this, duration: Duration(seconds: 3));
  18. bool isForward = true;
  19. _animationController.addStatusListener((status) {
  20. if (status == AnimationStatus.forward) {
  21. isForward = true;
  22. } else if (status == AnimationStatus.completed ||
  23. status == AnimationStatus.dismissed) {
  24. if (isForward) {
  25. _animationController.reverse();
  26. } else {
  27. _animationController.forward();
  28. }
  29. } else if (status == AnimationStatus.reverse) {
  30. isForward = false;
  31. }
  32. });
  33. _animationController.forward();
  34. }
  35. @override
  36. void dispose() {
  37. _animationController.dispose();
  38. super.dispose();
  39. }
  40. @override
  41. Widget build(BuildContext context) {
  42. return SingleChildScrollView(
  43. child: Center(
  44. child: Column(
  45. crossAxisAlignment: CrossAxisAlignment.center,
  46. children: <Widget>[
  47. AnimatedBuilder(
  48. animation: _animationController,
  49. builder: (BuildContext context, Widget child) {
  50. return Padding(
  51. padding: const EdgeInsets.symmetric(vertical: 16.0),
  52. child: Column(
  53. children: <Widget>[
  54. Wrap(
  55. spacing: 10.0,
  56. runSpacing: 16.0,
  57. children: <Widget>[
  58. GradientCircularProgressIndicator(
  59. // No gradient
  60. colors: [Colors.blue, Colors.blue],
  61. radius: 50.0,
  62. strokeWidth: 3.0,
  63. value: _animationController.value,
  64. ),
  65. GradientCircularProgressIndicator(
  66. colors: [Colors.red, Colors.orange],
  67. radius: 50.0,
  68. strokeWidth: 3.0,
  69. value: _animationController.value,
  70. ),
  71. GradientCircularProgressIndicator(
  72. colors: [Colors.red, Colors.orange, Colors.red],
  73. radius: 50.0,
  74. strokeWidth: 5.0,
  75. value: _animationController.value,
  76. ),
  77. GradientCircularProgressIndicator(
  78. colors: [Colors.teal, Colors.cyan],
  79. radius: 50.0,
  80. strokeWidth: 5.0,
  81. strokeCapRound: true,
  82. value: CurvedAnimation(
  83. parent: _animationController,
  84. curve: Curves.decelerate)
  85. .value,
  86. ),
  87. TurnBox(
  88. turns: 1 / 8,
  89. child: GradientCircularProgressIndicator(
  90. colors: [Colors.red, Colors.orange, Colors.red],
  91. radius: 50.0,
  92. strokeWidth: 5.0,
  93. strokeCapRound: true,
  94. backgroundColor: Colors.red[50],
  95. totalAngle: 1.5 * pi,
  96. value: CurvedAnimation(
  97. parent: _animationController,
  98. curve: Curves.ease)
  99. .value),
  100. ),
  101. RotatedBox(
  102. quarterTurns: 1,
  103. child: GradientCircularProgressIndicator(
  104. colors: [Colors.blue[700], Colors.blue[200]],
  105. radius: 50.0,
  106. strokeWidth: 3.0,
  107. strokeCapRound: true,
  108. backgroundColor: Colors.transparent,
  109. value: _animationController.value),
  110. ),
  111. GradientCircularProgressIndicator(
  112. colors: [
  113. Colors.red,
  114. Colors.amber,
  115. Colors.cyan,
  116. Colors.green[200],
  117. Colors.blue,
  118. Colors.red
  119. ],
  120. radius: 50.0,
  121. strokeWidth: 5.0,
  122. strokeCapRound: true,
  123. value: _animationController.value,
  124. ),
  125. ],
  126. ),
  127. GradientCircularProgressIndicator(
  128. colors: [Colors.blue[700], Colors.blue[200]],
  129. radius: 100.0,
  130. strokeWidth: 20.0,
  131. value: _animationController.value,
  132. ),
  133. Padding(
  134. padding: const EdgeInsets.symmetric(vertical: 16.0),
  135. child: GradientCircularProgressIndicator(
  136. colors: [Colors.blue[700], Colors.blue[300]],
  137. radius: 100.0,
  138. strokeWidth: 20.0,
  139. value: _animationController.value,
  140. strokeCapRound: true,
  141. ),
  142. ),
  143. //剪裁半圓
  144. ClipRect(
  145. child: Align(
  146. alignment: Alignment.topCenter,
  147. heightFactor: .5,
  148. child: Padding(
  149. padding: const EdgeInsets.only(bottom: 8.0),
  150. child: SizedBox(
  151. //width: 100.0,
  152. child: TurnBox(
  153. turns: .75,
  154. child: GradientCircularProgressIndicator(
  155. colors: [Colors.teal, Colors.cyan[500]],
  156. radius: 100.0,
  157. strokeWidth: 8.0,
  158. value: _animationController.value,
  159. totalAngle: pi,
  160. strokeCapRound: true,
  161. ),
  162. ),
  163. ),
  164. ),
  165. ),
  166. ),
  167. SizedBox(
  168. height: 104.0,
  169. width: 200.0,
  170. child: Stack(
  171. alignment: Alignment.center,
  172. children: <Widget>[
  173. Positioned(
  174. height: 200.0,
  175. top: .0,
  176. child: TurnBox(
  177. turns: .75,
  178. child: GradientCircularProgressIndicator(
  179. colors: [Colors.teal, Colors.cyan[500]],
  180. radius: 100.0,
  181. strokeWidth: 8.0,
  182. value: _animationController.value,
  183. totalAngle: pi,
  184. strokeCapRound: true,
  185. ),
  186. ),
  187. ),
  188. Padding(
  189. padding: const EdgeInsets.only(top: 10.0),
  190. child: Text(
  191. "${(_animationController.value * 100).toInt()}%",
  192. style: TextStyle(
  193. fontSize: 25.0,
  194. color: Colors.blueGrey,
  195. ),
  196. ),
  197. )
  198. ],
  199. ),
  200. ),
  201. ],
  202. ),
  203. );
  204. },
  205. ),
  206. ],
  207. ),
  208. ),
  209. );
  210. }
  211. }

怎么樣,很炫酷吧!GradientCircularProgressIndicator已經被添加進了筆者維護的 flukit 組件庫中了,讀者如果有需要,可以直接依賴 flukit 包。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號