Flutter: how to create generative animations using CustomPainter - flutter

I have used Flutter's CustomPainter class to create a generative still image using Paths (see code below). I'd like to be able to animate such images indefinitely. What is the most straightforward approach to doing this?
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: PathExample(),
),
);
class PathExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: PathPainter(),
);
}
}
class PathPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.grey[200]
..style = PaintingStyle.fill
..strokeWidth = 0.0;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
Path path2 = Path();
for (double i = 0; i < 200; i++) {
Random r = new Random();
path2.moveTo(sin(i / 2.14) * 45 + 200, i * 12);
path2.lineTo(sin(i / 2.14) * 50 + 100, i * 10);
paint.style = PaintingStyle.stroke;
paint.color = Colors.red;
canvas.drawPath(path2, paint);
}
Path path = Path();
paint.color = Colors.blue;
paint.style = PaintingStyle.stroke;
for (double i = 0; i < 30; i++) {
path.moveTo(100, 50);
// xC, yC, xC, yC, xEnd, yEnd
path.cubicTo(
-220, 300, 500, 600 - i * 20, size.width / 2 + 50, size.height - 50);
canvas.drawPath(path, paint);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

To do this, you're going to want to do a lot of what the TickerProviderStateMixin does - essentially, you need to create and manage your own Ticker.
I've done this in a simple builder widget below. It simply schedules a build every time there has been a tick, and then builds with the given value during that ticket. I've added a totalElapsed parameter as well as a sinceLastDraw parameter for convenience but you could easily choose one or the other depending on what's the most convenient for what you're doing.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(
MaterialApp(
home: PathExample(),
),
);
class PathExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TickerBuilder(builder: (context, sinceLast, total) {
return CustomPaint(
painter: PathPainter(total.inMilliseconds / 1000.0),
);
});
}
}
class TickerBuilder extends StatefulWidget {
// this builder function is used to create the widget which does
// whatever it needs to based on the time which has elapsed or the
// time since the last build. The former is useful for position-based
// animations while the latter could be used for velocity-based
// animations (i.e. oldPosition + (time * velocity) = newPosition).
final Widget Function(BuildContext context, Duration sinceLastDraw, Duration totalElapsed) builder;
const TickerBuilder({Key? key, required this.builder}) : super(key: key);
#override
_TickerBuilderState createState() => _TickerBuilderState();
}
class _TickerBuilderState extends State<TickerBuilder> {
// creates a ticker which ensures that the onTick function is called every frame
late final Ticker _ticker = Ticker(onTick);
// the total is the time that has elapsed since the widget was created.
// It is initially set to zero as no time has elasped when it is first created.
Duration total = Duration.zero;
// this last draw time is saved during each draw cycle; this is so that
// a time between draws can be calculated
Duration lastDraw = Duration.zero;
void onTick(Duration elapsed) {
// by calling setState every time this function is called, we're
// triggering this widget to be rebuilt on every frame.
// This is where the indefinite animation part comes in!
setState(() {
total = elapsed;
});
}
#override
void initState() {
super.initState();
_ticker.start();
}
#override
void didChangeDependencies() {
_ticker.muted = !TickerMode.of(context);
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final result = widget.builder(context, total - lastDraw , total);
lastDraw = total;
return result;
}
#override
void dispose() {
_ticker.stop();
super.dispose();
}
}
class PathPainter extends CustomPainter {
final double pos;
PathPainter(this.pos);
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.grey
..style = PaintingStyle.fill
..strokeWidth = 0.0;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
Path path2 = Path();
for (double i = 0; i < 200; i++) {
Random r = new Random();
path2.moveTo(sin(i / 2.14 + pos) * 45 + 200, (i * 12));
path2.lineTo(sin(i / 2.14 + pos) * 50 + 100, (i * 10));
paint.style = PaintingStyle.stroke;
paint.color = Colors.red;
canvas.drawPath(path2, paint);
}
Path path = Path();
paint.color = Colors.blue;
paint.style = PaintingStyle.stroke;
for (double i = 0; i < 30; i++) {
path.moveTo(100, 50);
// xC, yC, xC, yC, xEnd, yEnd
path.cubicTo(
-220,
300,
500,
600 - i * 20,
size.width / 2 + 50,
size.height - 50,
);
canvas.drawPath(path, paint);
}
}
// in this particular case, this is rather redundant as
// the animation is happening every single frame. However,
// in the case where it doesn't need to animate every frame, you
// should implement it such that it only returns true if it actually
// needs to redraw, as that way the flutter engine can optimize
// its drawing and use less processing power & battery.
#override
bool shouldRepaint(PathPainter old) => old.pos != pos;
}
A couple things to note - first off, the drawing is hugely sub-optimal in this case. Rather than re-drawing the background every frame, that could be made into a static background using a Container or DecoratedBox. Secondly, the paint objects are being re-created and used every single frame - if these are constant, they could be instantiated once and re-used over and over again.
Also, since WidgetBuilder is going to be running a lot, you're going to want to make sure that you do as little as possible in its build function - you're not going to want to build up a whole widget tree there but rather move it as low as possible in the tree so it only builds things that are actually animating (as I've done in this case).

Related

Why Flutter AnimationController becomes very laggy after repeating itself for 3 times

I have an AnimationController, and I want to repeat it every time the animation completes.
Let's say I want it to repeat for 4 times as is shown below:
// ......
// _sets is initialised as 4
_controller = AnimationController(
duration: Duration(seconds: _roundDuration), vsync: this);
_controller.addListener(() {
if (_controller.isCompleted) {
_sets--;
if (_sets > 0) {
_controller.reset();
_controller.forward();
} else {
_controller.stop();
}
}
});
super.initState();
}
The problem is that after repeating this process for 3 times, it becomes very laggy.
The value of the controller is passed to an AnimatedBuilder for driving my CustomPainter-based animation:
child: AnimatedBuilder(
builder: (_, __) {
return CustomPaint(
painter: MyPainter(
percentValue: _controller.value,
moveDuration: _moveDuration,
holdDuration: _holdDuration));
},
animation: _controller,
),
And my CustomPainter looks like this:
class MyPainter extends CustomPainter {
final double percentValue;
final int moveDuration;
final int holdDuration;
MyPainter(
{required this.percentValue,
required this.moveDuration,
required this.holdDuration});
#override
void paint(Canvas canvas, Size size) {
// print("paint percent value: $percentValue");
final holdingLinePaint = Paint()
..strokeWidth = 40
..color = Colors.amber
..strokeCap = StrokeCap.round;
final linePaint = Paint()
..strokeWidth = 40
..color = Colors.red
..strokeCap = StrokeCap.round;
const lineLength = 380;
const leftPadding = 10.0;
// animation duration
// moveDuration represents the two red ends and holdDuration represents the yellow part at the middle of the line
final totalDuration = 2 * moveDuration + holdDuration;
final dy = size.height / 2;
final lineStart = Offset(leftPadding, dy);
// lineEnd is animating according to the percentValue passed in
final lineEnd = Offset(percentValue * 380 + leftPadding, dy);
// line one
var firstEnd =
Offset((lineLength / totalDuration) * moveDuration + leftPadding, dy);
canvas.drawCircle(firstEnd, 10, Paint());
// line two
var secondStart = firstEnd;
var secondEnd = Offset(
(moveDuration + holdDuration) / totalDuration * lineLength +
leftPadding,
dy);
canvas.drawCircle(secondEnd, 10, Paint());
// line three
var thirdStart = secondEnd;
// divided into 3 phrases
if (percentValue < (moveDuration / totalDuration)) {
canvas.drawLine(lineStart, lineEnd, linePaint);
} else if (percentValue >= (moveDuration / totalDuration) &&
percentValue < (moveDuration + holdDuration) / totalDuration) {
canvas.drawLine(secondStart, lineEnd, holdingLinePaint);
canvas.drawLine(lineStart, firstEnd, linePaint);
} else {
canvas.drawLine(thirdStart, lineEnd, linePaint);
canvas.drawLine(secondStart, secondEnd, holdingLinePaint);
canvas.drawLine(lineStart, firstEnd, linePaint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Basically, I am just animating a line but have to draw it with different colors according to different phrases.
Any advice?

Why does my Flutter CustomPainter class not paint anything on the screen when using the canvas..drawArc() function with a sweepAngle less than 2*pi?

For some reason, my CustomPainter does not draw anything to the screen. I'm trying to build a Pie-Chart but the painter only works when I set the sweepAngle to 2*pi.
The Widget where CustomPaint is called has the following structure:
class PieChart extends StatelessWidget {
const PieChart({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: 160,
width: 210,
child: CustomPaint(
painter: PieChartPainter(categories: dataset, width: 10),
),
);
}
}
This is my CustomPainter class:
class PieChartPainter extends CustomPainter {
PieChartPainter({
required this.categories,
required this.width,
});
final List<Category> categories;
final double width;
#override
void paint(Canvas canvas, Size size) {
Offset center = Offset(size.width / 2, size.height / 2);
double radius = min(size.width / 2, size.height / 2);
double total = 0;
// Calculate total amount from each category
for (var expense in categories) {
total += expense.amount;
}
// The angle/radian at 12 o'clock
double startRadian = -pi / 2;
for (var index = 0; index < categories.length; index++) {
final currentCategory = categories.elementAt(index);
final sweepRadian = currentCategory.amount / total * 2 * pi;
final paint = Paint()
..style = PaintingStyle.fill
..strokeWidth = width
..color = categoryColors.elementAt(index % categories.length);
final rect = Rect.fromCenter(
center: center, width: radius * 2, height: radius * 2);
canvas.drawArc(
rect,
startRadian,
2 * pi, // should really be "sweepRadian"
false,
paint,
);
startRadian += sweepRadian;
}
}
#override
bool shouldRepaint(PieChartPainter oldDelegate) {
return true;
}
}
I can almost certainly say that the problem has nothing to do with the data and colors that I provide, because whenever I log the current elements to the console, it gives the correct output.
Here, you can see my Widget structure:
And here is an image of the component itself: (Notice that only the last category-color is shown for the entire circle, because whenever I use a sweepAngle that is less than 2*pi, the entire widget doesn't show colors.)
This is the component when I set a sweepAngle that is less than 2*pi:
I really cannot figure out what might be causing this issue. Does anyone have an Idea what else is influenced by the sweepAngle parameter? I also have no idea why the colored circles to the left of the individual categories are not visible, because they're rendered in an entirely different Widget-branch...
If you have any idea on how to solve this, I would be more than happy to provide more information but as long as I don't know where to look, I don't want to spam this issue with unnecessary information.
For anyone wondering:
The problem had to do with the "--enable-software-rendering" argument. Once I ran flutter with flutter run --enable-software-rendering, it all worked as expected.

How to draw a curved line chart with CustomPainter?

I'm trying to draw a line graph with some smooth curves, like this one from fl_chart :
I tried using quadraticBezierTo, but it didn't work :
This is my code, is there any other way?
import 'package:flutter/material.dart';
class DrawLines extends StatefulWidget {
final Coordinates lineCoordinates;
final double yHeight;
DrawLines({required this.lineCoordinates, required this.yHeight});
#override
_DrawLinesState createState() => _DrawLinesState();
}
class _DrawLinesState extends State<DrawLines> {
#override
Widget build(BuildContext context) {
return CustomPaint(
foregroundPainter: LinePainter(
coordinates: widget.lineCoordinates, yHeight: widget.yHeight),
);
}
}
class CoordValues {
late double x;
late double y;
CoordValues({required this.x, required this.y});
}
class Coordinates {
late CoordValues startCoords;
late CoordValues endCoords;
Coordinates({required this.startCoords, required this.endCoords});
}
class LinePainter extends CustomPainter {
late Coordinates coordinates;
late double yHeight;
LinePainter({required this.coordinates, required this.yHeight});
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Color(0xFF295AE3)
..strokeCap = StrokeCap.round
..strokeWidth = 2;
//curved
Path path = Path();
path.moveTo(coordinates.startCoords.x, yHeight - coordinates.startCoords.y);
path.quadraticBezierTo(
coordinates.startCoords.x + 15,
coordinates.startCoords.y,
coordinates.endCoords.x + 15,
yHeight - coordinates.endCoords.y);
//for every two points one line is drawn
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
I think my question is pretty straightforward but somehow stackoverflow wants me to write more.
Path drawPath(bool closePath) {
final width = MediaQuery.of(context).size.width;
final height = chartHeight;
final path = Path();
final segmentWidth = width / 3 / 2;
path.moveTo(0, height);
path.cubicTo(segmentWidth, height, 2 * segmentWidth, 0, 3 * segmentWidth, 0);
path.cubicTo(4 * segmentWidth, 0, 5 * segmentWidth, height, 6 * segmentWidth, height);
return path;
}
This tutorial is great for this purpose and everything explained well. just check the link below
https://www.kodeco.com/32557465-curved-line-charts-in-flutter
you can use fl_chart dependency here is the link

Flutter - Reuse previously painted canvas in a CustomPainter

I have a CustomPainter that I want to render some items every few milliseconds. But I only want to render the items that have changed since the last draw. I plan on manually clearing the area that will be changing and redrawing just in the area. The problem is that the canvas in Flutter seems to be completely new every time paint() is called. I understand that I can keep track of the entire state and redraw everything every time, but for performance reasons and the specific use case that is not preferable. Below is sample code that could represent the issue:
I understand that everything will need to be redrawn when the canvas size changes.
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
class CanvasWidget extends StatefulWidget {
CanvasWidget({Key key}) : super(key: key);
#override
_CanvasWidgetState createState() => _CanvasWidgetState();
}
class _CanvasWidgetState extends State<CanvasWidget> {
final _repaint = ValueNotifier<int>(0);
TestingPainter _wavePainter;
#override
void initState() {
_wavePainter = TestingPainter(repaint: _repaint);
Timer.periodic( Duration(milliseconds: 50), (Timer timer) {
_repaint.value++;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _wavePainter,
);
}
}
class TestingPainter extends CustomPainter {
static const double _numberPixelsToDraw = 3;
final _rng = Random();
double _currentX = 0;
double _currentY = 0;
TestingPainter({Listenable repaint}): super(repaint: repaint);
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.transparent;
if(_currentX + _numberPixelsToDraw > size.width)
{
_currentX = 0;
}
// Clear previously drawn points
var clearArea = Rect.fromLTWH(_currentX, 0, _numberPixelsToDraw, size.height);
canvas.drawRect(clearArea, paint);
Path path = Path();
path.moveTo(_currentX, _currentY);
for(int i = 0; i < _numberPixelsToDraw; i++)
{
_currentX++;
_currentY = _rng.nextInt(size.height.toInt()).toDouble();
path.lineTo(_currentX, _currentY);
}
// Draw new points in red
paint.color = Colors.red;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Redrawing the whole canvas, even on every frame, is completely efficient. Trying to reuse the previous frame will often not be more efficient.
Looking at the code you posted, there are certain areas with rooms for improvement, but trying to preserve parts of the canvas should not be one of them.
The real performance issue you are having, is from repeatedly changing a ValueNotifier from a Timer.periodic event, every 50 ms. A much better way to handle redrawing on every frame, is to use AnimatedBuilder with a vsync, so the paint method of the CustomPainter will be called on every frame. This is similar to Window.requestAnimationFrame in the web browser world, if you are familiar with that. Here vsync stands for "vertical sync", if you are familiar with how computer graphics work. Essentially, your paint method will be called 60 times per second, on a device with 60 Hz screen, and it'll paint 120 times per second on a 120 Hz screen. This is the correct and scalable way to achieve buttery smooth animation across different kind of devices.
There are other areas worth optimizing, before thinking about preserving parts of the canvas. For example, just briefly looking at your code, you have this line:
_currentY = _rng.nextInt(size.height.toInt()).toDouble();
Here I assume you want to have a random decimal between 0 and size.height, if so, you can simply write _rng.nextDouble() * size.height, instead of casting a double to int and back again, and (probably unintentionally) rounding it during that process. But the performance gain from stuff like these is negligible.
Think about it, if a 3D video game can run smoothly on a phone, with each frame being dramatically different from the previous one, your animation should run smoothly, without having to worry about manually clearing parts of the canvas. Trying to manually optimize the canvas will probably lead to performance loss instead.
So, what you really should be focusing, is to use AnimatedBuilder instead of Timer to trigger the canvas redraw in your project, as a starting point.
For example, here's a small demo I made using AnimatedBuilder and CustomPaint:
Full source code:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
List<SnowFlake> snowflakes = List.generate(100, (index) => SnowFlake());
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)..repeat();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue, Colors.lightBlue, Colors.white],
stops: [0, 0.7, 0.95],
),
),
child: AnimatedBuilder(
animation: _controller,
builder: (_, __) {
snowflakes.forEach((snow) => snow.fall());
return CustomPaint(
painter: MyPainter(snowflakes),
);
},
),
),
);
}
}
class MyPainter extends CustomPainter {
final List<SnowFlake> snowflakes;
MyPainter(this.snowflakes);
#override
void paint(Canvas canvas, Size size) {
final w = size.width;
final h = size.height;
final c = size.center(Offset.zero);
final whitePaint = Paint()..color = Colors.white;
canvas.drawCircle(c - Offset(0, -h * 0.165), w / 6, whitePaint);
canvas.drawOval(
Rect.fromCenter(
center: c - Offset(0, -h * 0.35),
width: w * 0.5,
height: w * 0.6,
),
whitePaint);
snowflakes.forEach((snow) =>
canvas.drawCircle(Offset(snow.x, snow.y), snow.radius, whitePaint));
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class SnowFlake {
double x = Random().nextDouble() * 400;
double y = Random().nextDouble() * 800;
double radius = Random().nextDouble() * 2 + 2;
double velocity = Random().nextDouble() * 4 + 2;
SnowFlake();
fall() {
y += velocity;
if (y > 800) {
x = Random().nextDouble() * 400;
y = 10;
radius = Random().nextDouble() * 2 + 2;
velocity = Random().nextDouble() * 4 + 2;
}
}
}
Here I'm generating 100 snowflakes, redrawing the whole screen every frame. You can easily change the number of snowflakes to 1000 or higher, and it would still run very smoothly. Here I'm also not using the device screen size as much as I should be, as you can see, there are some hardcoded values like 400 or 800. Anyway, hopefully this demo would give you some faith in Flutter's graphics engine. :)
Here is another (smaller) example, showing you everything you need to get going with Canvas and Animations in Flutter. It might be easier to follow:
import 'package:flutter/material.dart';
void main() {
runApp(DemoWidget());
}
class DemoWidget extends StatefulWidget {
#override
_DemoWidgetState createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)..repeat(reverse: true);
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (_, __) => CustomPaint(
painter: MyPainter(_controller.value),
),
);
}
}
class MyPainter extends CustomPainter {
final double value;
MyPainter(this.value);
#override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
value * size.shortestSide,
Paint()..color = Colors.blue,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
The only solution currently available is to capture the progress as image and then draw the image instead of executing the whole canvas code.
for drawing the image you can use canvas.drawImage as mentioned by pskink in the above comment.
but the solution i would recommend is to wrap the CustomPaint with RenderRepaint to convert that widget to image. for detials refer to
Creating raw image from Widget or Canvas and (https://medium.com/flutter-community/export-your-widget-to-image-with-flutter-dc7ecfa6bafb for brief implementation), and have a condition to check if you are building for the first time or not.
class _CanvasWidgetState extends State<CanvasWidget> {
/// Just to track if its the first frame or not.
var _flag = false;
/// Will be used for generating png image.
final _globalKey = new GlobalKey();
/// Stores the image bytes
Uint8List _imageBytes;
/// No need for this actually;
/// final _repaint = ValueNotifier<int>(0);
TestingPainter _wavePainter;
Future<Uint8List> _capturePng() async {
try {
final boundary = _globalKey
.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
var pngBytes = byteData.buffer.asUint8List();
var bs64 = base64Encode(pngBytes);
print(pngBytes);
print(bs64);
setState(() {});
return pngBytes;
} catch (e) {
print(e);
}
}
#override
void initState() {
_wavePainter = TestingPainter();
Timer.periodic( Duration(milliseconds: 50), (Timer timer) {
if (!flag) flag = true;
/// Save your image before each redraw.
_imageBytes = _capturePng();
/// You don't need a listener if you are using a stful widget.
/// It will do just fine.
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return RepaintBoundary(
key: _globalkey,
child: Container(
/// Use this if this is not the first frame.
decoration: _flag ? BoxDecoration(
image: DecorationImage(
image: MemoryImage(_imageBytes)
)
) : null,
child: CustomPainter(
painter: _wavePainter
)
)
);
}
}
This way the image will not be a part of your custom painter and let me tell you, i tried drawing image using canvas but it was not that efficient, the MemoryImage provided by flutter renders image in a much better way.

Flutter : How to set dynamic color in CustomPainter object

Setting paint color dynamically for CustomPainter's constructor not working.
lines_painter.dart
class LinesPainter extends CustomPainter {
final double lineHeight = 8;
final int maxLines = 60;
final Color customColor;
LinesPainter(this.customColor);
#override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
canvas.save();
final Paint linePainter = Paint()
..color = customColor
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
final radius = size.width / 2;
List.generate(maxLines, (i) {
var newRadius = (i % 5 == 0) ? radius - 15 : radius - 5;
canvas.drawLine(Offset(0, radius), Offset(0, newRadius), linePainter);
canvas.rotate(2 * pi / maxLines);
});
canvas.restore();
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
utils.dart
class Utils{
List<Color> getColorsArray (){
return [
Color(0xff5733),
Color(0xc70039),
Color(0x900c3e),
Color(0x571845),
Color(0x251e3e),
Color(0x051e3e),
];
}
}
Below code should paint Round shape with lines
LinesPainter(Utils().getColorsArray()[0])
Expected result:
Current result:
As #pskink mentioned in comments I have read the documentation and i got the idea that I am missing the Alpha value in hex code.
Make a change in utils.dart file like below and it works fine for me.
class Utils{
List<Color> getColorsArray (){
return [
Color(0xffff5733),
Color(0xffc70039),
Color(0xff900c3e),
Color(0xff571845),
Color(0xff251e3e),
Color(0xff051e3e),
];
}
}