I'm picking up flutter for what's supposed to be a quick bit of app work. I must be missing something. Can someone please explain how stateful components fit together? If I have a webservice API that provides a data structure as JSON and I want to display parts of that in my app and I want to break the display down into components, how do the components get associated with the different bits of JSON?
At its simplest, I have a component to display a battery level:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class BatteryPainter extends CustomPainter {
double level;
BatteryPainter({this.level = 0.75});
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.green
..strokeWidth = 5
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke;
Rect rect = Rect.fromCenter(
center: Offset(size.width / 2, size.height),
width: size.width * 0.9,
height: 50,
);
RRect rr = RRect.fromRectAndRadius(rect, const Radius.circular(5));
canvas.drawRRect(rr, paint);
rect = Rect.fromCenter(
center: Offset(size.width / 2, size.height),
width: size.width * 0.9 - 10,
height: 40
);
double right = rect.left + (rect.right - rect.left) * level;
rect = Rect.fromLTRB(rect.left, rect.top, right, rect.bottom);
rr = RRect.fromRectAndRadius(rect, const Radius.circular(5));
paint
..style = PaintingStyle.fill;
canvas.drawRRect(rr, paint);
}
#override
bool shouldRepaint(BatteryPainter oldDelegate) => oldDelegate.level != level;
}
class Battery extends StatefulWidget {
#override
_BatteryWidgetState createState() => _BatteryWidgetState();
}
class _BatteryWidgetState extends State<Battery> {
Widget build (BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return CustomPaint(
painter:BatteryPainter(),
size:Size(constraints.maxWidth, 50)
);
});
}
}
But now if I create one of these:
Widget build(BuildContext context) {
return Container(
child: Column(children: [
...,
Battery()
]
);
}
How do I pass updates to the battery state in so that the component gets redrawn? If I keep a reference to the Battery object, that does me no good because it's the state that keeps the current battery level, not the widget, so there's no obvious way to change the level value. If I pass a level into the Battery() constructor and the use that value in createState that doesn't really help either, since Dart is pass-by-value so changing the value in the top-level component which knows about changes to this value don't change the value in the battery level display component.
How are fine-grained components meant to be connected to a model like this?
How do I pass updates to the battery state in so that the component gets redrawn?
There is no simple answer. You pick one of the many state management methods and go with it.
Related
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.
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
I'm learning Flutter and am tasked with creating some 3D type effects for transitions and other "wow" factor type things. I have been using flutter's transforms to achieve this but have run into a problem. When I rotate about the Y axis the plane containing whatever tab was showing disappears around the 80 degree mark and shows back up around the 100 degree mark. This does not happen when rotated at -90 degrees.
I've seen other answers posted such as transform: rotateY() making element disappear but none seem to cover this issue.
The question is; is there something I should be setting to avoid this since it is pretty close to off screen when I do it. If not is this a quirk or known bug in flutter's transition?
I am unable to put the full code and I'm not really familiar enough with flutter to rip out a bit and rough up an example quickly. Below is a code snippet and I'll come back with a better code snippet once I have more time. My apologies for that.
#override
Container build (BuildContext context) {
return Container(
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.004)
..translate(position.x, position.y, position.z)
..rotateX(rotation.x)
..rotateY(rotation.y)
..rotateZ(rotation.z),
child: CustomPaint(
painter: MyPainter(),
),
),
);
}
class MyPainter extends CustomPainter {
Size rectSize = Size(100, 100);
#override
Future<void> paint(Canvas canvas, Size size) async {
final paint = Paint()
..style = PaintingStyle.fill
..color = Colors.green;
Rect rect = Offset(-rectSize.width / 2.0, -rectSize.height / 2.0) & this.rectSize;
canvas.drawRect(rect, paint);
}
}
EDIT 1: I have tried to get a minimal example working but the rotation actually works as expected. I used the above code with no changes into a statefulwidget but the plane only disappears at 90 and 270 degrees as expected. I'll update the question if I find out anything else. Thanks to all who have looked at this.
After quite a number of hours since yesterday I have discovered the cause of the disappearance. Both fragments of code are held in a custom Container class which uses alignment: FractionalOffset.center to align them into the center of the screen. When this is removed the widgets return to the upper left hand corner and rotates as expected.
I'm not sure why this happens and would love to know if anybody has any ideas. However, below is a minimal replication of the issue. Uncomment the aligment: FractionalOffset.center line in runApp() to see the error in action.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as Vector;
import 'dart:math';
void main() {
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: Stack(
// alignment: FractionalOffset.center, // Remove the comment slashes to see issue
children: [
MyWidget(),
]
)
),
);
//runApp(MyApp());
}
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Vector.Vector3 position = Vector.Vector3.zero();
Vector.Vector3 rotation = Vector.Vector3.zero();
int degrees = 0;
#override
initState() {
super.initState();
new Timer.periodic(Duration(milliseconds: 100), (timer) => {timerCall()});
}
void timerCall() {
setState(() {
});
}
#override
Container build (BuildContext context) {
degrees += 1;
if (degrees >= 360) degrees = 0;
rotation.y = degrees * (pi / 180);
return Container(
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.004)
..translate(position.x, position.y, position.z)
..rotateX(rotation.x)
..rotateY(rotation.y)
..rotateZ(rotation.z),
child: CustomPaint(
painter: MyPainter(),
),
),
);
}
}
class MyPainter extends CustomPainter {
Size rectSize = Size(200, 200);
#override
Future<void> paint(Canvas canvas, Size size) async {
final paint = Paint()
..style = PaintingStyle.fill
..color = Colors.green;
Rect rect = Offset(-rectSize.width / 2.0, -rectSize.height / 2.0) & this.rectSize;
canvas.drawRect(rect, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
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).
I am testing the performance in drawing using Flutter. I am using Path to draw line between each point detected by Listener because I have read that the performance would increase using it. I am using Listener because I tried also the Apple Pencil on iPad 2017 by changing the kind property to stylus.
The problem is that I was hoping to get a response in the stroke design similar to notability, it seems much slower, acceptable but not as much as I would like.
So I'm looking for tips to increase performance in terms of speed.
At the following link they recommended using NotifyListener(), but I didn't understand how to proceed. If it really improves performance I would like even an example to be able to implement it.
If Flutter has some limitations when it comes to drawing with your fingers or with a stylus then let me know.
performance issue in drawing using flutter
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
class DrawWidget extends StatefulWidget {
#override
_DrawWidgetState createState() => _DrawWidgetState();
}
class _DrawWidgetState extends State<DrawWidget> {
Color selectedColor = Colors.black;
double strokeWidth = 3.0;
List<MapEntry<Path, Object>> pathList = List();
StrokeCap strokeCap = (Platform.isAndroid) ? StrokeCap.butt : StrokeCap.round;
double opacity = 1.0;
Paint pa = Paint();
#override
Widget build(BuildContext context) {
return Listener(
child: CustomPaint(
size: Size.infinite,
painter: DrawingPainter(
pathList: this.pathList,
),
),
onPointerDown: (details) {
if (details.kind == PointerDeviceKind.touch) {
print('down');
setState(() {
Path p = Path();
p.moveTo(details.localPosition.dx, details.localPosition.dy);
pa.strokeCap = strokeCap;
pa.isAntiAlias = true;
pa.color = selectedColor.withOpacity(opacity);
pa.strokeWidth = strokeWidth;
pa.style = PaintingStyle.stroke;
var drawObj = MapEntry<Path,Paint>(p, pa);
pathList.add(drawObj);
});
}
},
onPointerMove: (details) {
if (details.kind == PointerDeviceKind.touch) {
print('move');
setState(() {
pathList.last.key.lineTo(details.localPosition.dx, details.localPosition.dy);
});
}
},
/*onPointerUp: (details) {
setState(() {
});
},*/
);
}
}
class DrawingPainter extends CustomPainter {
DrawingPainter({this.pathList});
List<MapEntry<Path, Object>> pathList;
#override
void paint(Canvas canvas, Size size) {
for(MapEntry<Path, Paint> m in pathList) {
canvas.drawPath(m.key, m.value);
}
}
#override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
I think you should not use setState, rather use state management like Bloc or ChangeNotifier or smth.
Also, just drawing a path with this:
canvas.drawPath(m.key, m.value);
Works for only small stroke widths, it leaves a weird-looking line full of blank spaces when drawing.
I implemented this by using Bloc that updates the UI based on the gesture functions (onPanStart, onPanEnd, onPanUpdate). It holds a List of a data model that I called CanvasPath that represents one line (so from onPanStart to onPanEnd events), and it holds the resulting Path of that line, list of Offsets and Paint used to paint it.
In the end paint() method draws every single Path from this CanvasPath object and also a circle in every Offset.
` for every canvasPath do this:
canvas.drawPath(canvasPath.path, _paint);
for (int i = 0; i < canvasPath.drawPoints.length - 1; i++) {
//draw Circle on every Offset of user interaction
canvas.drawCircle(
canvasPath.drawPoints[i],
_raidus,
_paint);
}`
I made a blog about this, where it is explained in much more details:
https://ivanstajcer.medium.com/flutter-drawing-erasing-and-undo-with-custompainter-6d00fec2bbc2