It's just to share a little skill of flutter. Of course, it's not only applicable to flutter, but also to other devices like Android. In order to quickly realize the requirements of xx design, we can take out the system's own widget s and change them into our own, with speed and style, but men can't say no!
First, look at the figure. Figure 1 shows the effect of the system. Figure 2 shows what we need to achieve. It's relatively simple. At that time, it was the loading effect on our company's web page. We need to use the shuttle web to realize the reality. We are in a hurry to directly change the control of the system. In other words, we have changed several things, but we can do it with simple pictures and switching states. It's a pity that we didn't use them A kind of
Figure 1
Figure two
Because it's recording screen to gif, the effect is not so good, please wait.
step
- Analyze the source code. The circular progress indicator of the system is also drawn by inheriting the CustomPainter. Here is the key code
//In general, use drawArc to draw the figure, and then use animation to change the initial angle and sector @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = valueColor ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke; if (backgroundColor != null) { final Paint backgroundPaint = Paint() ..color = backgroundColor ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke; canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint); } if (value == null) // Indeterminate paint.strokeCap = StrokeCap.square; canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint); }
- Analysis of target structure: see the figure above, our goal is that four dots are rotating. OK, let's draw four dots first.
@override void paint(Canvas canvas, Size size) { valueColorPaint = Paint() ..color = valueColor ..style = PaintingStyle.fill; if (backgroundColor != null) { valueColorPaint = Paint() ..color = backgroundColor ..style = PaintingStyle.fill; } if (oneLeadColor != null) { oneLeadCirclePaint = Paint() ..color = oneLeadColor ..style = PaintingStyle.fill; } else { oneLeadCirclePaint = valueColorPaint; } double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180); double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180); double dx2 = size.width + size.width * math.cos(arcSweepTwo * math.pi / 180); double dy2 = size.height + size.width * math.sin(arcSweepTwo * math.pi / 180); double dx3 = size.width + size.width * math.cos(arcSweepThree * math.pi / 180); double dy3 = size.height + size.width * math.sin(arcSweepThree * math.pi / 180); double dx4 = size.width + size.width * math.cos(arcSweepFour * math.pi / 180); double dy4 = size.height + size.width * math.sin(arcSweepFour * math.pi / 180); canvas.drawCircle(Offset(dx1, dy1), size.width / 2, valueColorPaint); canvas.drawCircle(Offset(dx2, dy2), size.width / 2, oneLeadCirclePaint); canvas.drawCircle(Offset(dx3, dy3), size.width / 2, valueColorPaint); canvas.drawCircle(Offset(dx4, dy4), size.width / 2, valueColorPaint); }
Three paints are used to distinguish colors. Because we are drawing circles, we need to determine the center coordinates. This is the key code
double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180); double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);
Come on, you're welcome. Take a look at the picture first
The square is an auxiliary figure, which determines the position of four circles at the initial position. At this time, the position of the circle is fixed, and then the coordinate of the center of the circle is (x,y) , the position has been confirmed, it's time for the small circle to move, and the path of the center of the small circle is the big circle in the figure. The logic diagram of Shenma has been analyzed, and the rest is mathematical calculation. We calculate based on half of the square, and the path of the center of the circle is the path of the radius of the big circle, the angle of the radius of the big circle If the degree is a variable, you can easily make the small circle move, so the center coordinate of the first quadrant circle is (arcsweetone is the angle that the big circle sweeps)
double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180); double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);
Then combine Tween animation, dynamically transfer the changed coordinates in, and the position of the small circle will change in real time
Take a look at the complete code, and don't post GitHub, which is a simple small example.
import 'dart:math' as math; import 'package:flutter/material.dart'; const double _kMinCircularProgressIndicatorSize = 8.0; class FlowerLoadingIndicator extends ProgressIndicator { /// Creates a Flower progress indicator. /// /// {@macro flutter.material.progressIndicator.parameters} const FlowerLoadingIndicator({ Key key, double value, Color backgroundColor, Animation<Color> valueColor, this.oneLeadColor, this.milliseconds, String semanticsLabel, String semanticsValue, }) : super( key: key, value: value, backgroundColor: backgroundColor, valueColor: valueColor, semanticsLabel: semanticsLabel, semanticsValue: semanticsValue, ); final Color oneLeadColor; final int milliseconds; Color getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor; Color getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor; Color getOneLeadColor(BuildContext context) => oneLeadColor ?? Theme.of(context).primaryColorLight; Widget _buildSemanticsWrapper({ @required BuildContext context, @required Widget child, }) { String expandedSemanticsValue = semanticsValue; if (value != null) { expandedSemanticsValue ??= '${(value * 100).round()}%'; } return Semantics( label: semanticsLabel, value: expandedSemanticsValue, child: child, ); } @override State<StatefulWidget> createState() => _FlowerLoadingIndicator(); } final Tween<double> _kRotationTweenOne = new Tween(begin: 0.0, end: 360.0); final Tween<double> _kRotationTweenTwo = new Tween(begin: -90.0, end: 270.0); final Tween<double> _kRotationTweenThree = new Tween(begin: 90, end: 450.0); final Tween<double> _kRotationTweenFour = new Tween(begin: 180, end: 540.0); class _FlowerLoadingIndicator extends State<FlowerLoadingIndicator> with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(milliseconds: widget.milliseconds ?? 1500), vsync: this, ); _controller.repeat(); } @override void didUpdateWidget(FlowerLoadingIndicator oldWidget) { super.didUpdateWidget(oldWidget); _controller.repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return _buildAnimation(); } Widget _buildIndicator(BuildContext context, double arcSweepOne, double arcSweepTwo, double arcSweepThree, double arcSweepFour) { return widget._buildSemanticsWrapper( context: context, child: Container( constraints: const BoxConstraints( minWidth: _kMinCircularProgressIndicatorSize, minHeight: _kMinCircularProgressIndicatorSize, ), child: CustomPaint( painter: _FlowerLoadingIndicatorPainter( backgroundColor: widget.backgroundColor, valueColor: widget.getValueColor(context), oneLeadColor: widget.getOneLeadColor(context), arcSweepOne: arcSweepOne, arcSweepTwo: arcSweepTwo, arcSweepThree: arcSweepThree, arcSweepFour: arcSweepFour, ), ), ), ); } Widget _buildAnimation() { return AnimatedBuilder( animation: _controller, builder: (BuildContext context, Widget child) { return _buildIndicator( context, _kRotationTweenOne.evaluate(_controller), _kRotationTweenTwo.evaluate(_controller), _kRotationTweenThree.evaluate(_controller), _kRotationTweenFour.evaluate(_controller), ); }, ); } } class _FlowerLoadingIndicatorPainter extends CustomPainter { _FlowerLoadingIndicatorPainter({ this.backgroundColor, this.valueColor, this.arcSweepOne, this.arcSweepTwo, this.arcSweepThree, this.arcSweepFour, this.oneLeadColor, }); final Color backgroundColor; final Color valueColor; final Color oneLeadColor; final double arcSweepOne; final double arcSweepTwo; final double arcSweepThree; final double arcSweepFour; Paint valueColorPaint; Paint oneLeadCirclePaint; @override void paint(Canvas canvas, Size size) { valueColorPaint = Paint() ..color = valueColor ..style = PaintingStyle.fill; if (backgroundColor != null) { valueColorPaint = Paint() ..color = backgroundColor ..style = PaintingStyle.fill; } if (oneLeadColor != null) { oneLeadCirclePaint = Paint() ..color = oneLeadColor ..style = PaintingStyle.fill; } else { oneLeadCirclePaint = valueColorPaint; } double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180); double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180); double dx2 = size.width + size.width * math.cos(arcSweepTwo * math.pi / 180); double dy2 = size.height + size.width * math.sin(arcSweepTwo * math.pi / 180); double dx3 = size.width + size.width * math.cos(arcSweepThree * math.pi / 180); double dy3 = size.height + size.width * math.sin(arcSweepThree * math.pi / 180); double dx4 = size.width + size.width * math.cos(arcSweepFour * math.pi / 180); double dy4 = size.height + size.width * math.sin(arcSweepFour * math.pi / 180); canvas.drawCircle(Offset(dx1, dy1), size.width / 2, valueColorPaint); canvas.drawCircle(Offset(dx2, dy2), size.width / 2, oneLeadCirclePaint); canvas.drawCircle(Offset(dx3, dy3), size.width / 2, valueColorPaint); canvas.drawCircle(Offset(dx4, dy4), size.width / 2, valueColorPaint); } @override bool shouldRepaint(_FlowerLoadingIndicatorPainter oldPainter) { return true; } }