I'm trying to draw some pointing triangles on CustomPaint and I'm facing performance issues when I change the shape of the triangle. If you execute the current code the refresh rate is fast.
If you change the part Offset(x,y+8), with Offset(x,y+4), the triangle shape changes slightly but the performance drops and the refresh rate is poor.
Does anyone knows why that happens and how can I resolve this issue?
import 'dart:math' as Math;
import 'package:flutter/material.dart';
void main() =>
runApp(
MaterialApp(
title: 'Demo',
home: Scaffold(
body: Shapes()
),
)
);
class Shapes extends StatefulWidget {
#override
_ShapesState createState() => new _ShapesState();
}
class _ShapesState extends State<Shapes> with SingleTickerProviderStateMixin{
Animation<double> animation;
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
)..addListener(() => setState(() {}));
animation = Tween<double>(
begin: 50.0,
end: 120.0,
).animate(animationController);
animationController.forward();
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.repeat();
}
});
}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: ShapesPainter());
}
}
class ShapesPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.strokeWidth = 2;
for (var i = 0; i < 1000; i++) {
Math.Random rnd = new Math.Random();
double x = 5 + rnd.nextInt(300 - 5).toDouble();
double y = 5 + rnd.nextInt(1200 - 5).toDouble();
final Path path = Path();
path.addPolygon([
Offset(x+6,y+8),
Offset(x,y+-8),
Offset(x-6,y+8),
Offset(x,y+8),
], true);
paint.color = Color(0xFF3f6cbf);
paint.style = PaintingStyle.stroke;
canvas.drawPath(path, paint);
paint.color = Color.fromARGB(255, 255, 0, 0);
paint.style = PaintingStyle.fill;
canvas.drawPath(path, paint);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Related
I want to create a line that animates to multiple offset points until the full line is painted out, using CustomPainter in Flutter.
I have almost achieved this effect, by using an animation object to tween to each new point, and an index to track the line progress.
Then, in CustomPainter, I paint 2 lines. One to line animates to the new position, and a second which draws the existing path based off the index.
However, there is a small UI error as the GIF shows, where the corners 'fill out' after a new point is added.
Note, a I tried using a TweenSequence borrowing the idea mentioned in this recent video but couldn't get it to work. FlutterForward, Youtube Video - around 14:40
import 'package:flutter/material.dart';
class LinePainterAnimation extends StatefulWidget {
const LinePainterAnimation({Key? key}) : super(key: key);
#override
State<LinePainterAnimation> createState() => _LinePainterAnimationState();
}
class _LinePainterAnimationState extends State<LinePainterAnimation>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
final List<Offset> _offsets = [
const Offset(50, 300),
const Offset(150, 100),
const Offset(300, 300),
const Offset(200, 300),
];
int _index = 0;
Offset _begin = const Offset(0, 0);
Offset _end = const Offset(0, 0);
#override
void initState() {
_begin = _offsets[0];
_end = _offsets[1];
_controller = AnimationController(
duration: const Duration(seconds: 1), vsync: this)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_index++;
if (_index < _offsets.length - 1) {
_begin = _offsets[_index];
_end = _offsets[_index + 1];
_controller.reset();
_controller.forward();
setState(() {});
}
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
Animation<Offset> animation =
Tween<Offset>(begin: _begin, end: _end).animate(_controller);
return Scaffold(
body: AnimatedBuilder(
animation: _controller,
builder: (context, child) => CustomPaint(
painter: LinePainter(
startOffset: _begin,
endOffset: animation.value,
offsets: _offsets,
index: _index,
),
child: Container(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.reset();
_controller.forward();
_begin = _offsets[0];
_end = _offsets[1];
_index = 0;
setState(() {});
},
child: const Text('Play'),
),
);
}
}
class LinePainter extends CustomPainter {
final Offset startOffset;
final Offset endOffset;
final List<Offset> offsets;
final int index;
LinePainter({
required this.startOffset,
required this.endOffset,
required this.offsets,
required this.index,
});
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.red
..strokeWidth = 20
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
var pathExisting = Path();
pathExisting.moveTo(offsets[0].dx, offsets[0].dy);
for (int i = 0; i < index + 1; i++) {
pathExisting.lineTo(offsets[i].dx, offsets[i].dy);
}
var pathNew = Path();
pathNew.moveTo(startOffset.dx, startOffset.dy);
pathNew.lineTo(endOffset.dx, endOffset.dy);
canvas.drawPath(pathNew, paint);
canvas.drawPath(pathExisting, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
OK - so I finally came up with a solution after a bit more research, which is perfect for this use case and animating complex paths - [PathMetrics][1]
So to get this working the basic steps are 1) define any path 2) calculate & extract this path using PathMetrics 3) then animate this path over any duration, based on the 0.0 to 1.0 value produced by the animation controller, and voilĂ it works like magic!
Note, the references I found to get this working: [Moving along a curved path in flutter][2] & [Medium article][3]
Updated code pasted below if this is helpful to anyone.
[![Solution][4]]
import 'dart:ui';
import 'package:flutter/material.dart';
class LinePainterAnimation extends StatefulWidget {
const LinePainterAnimation({Key? key}) : super(key: key);
#override
State<LinePainterAnimation> createState() => _LinePainterAnimationState();
}
class _LinePainterAnimationState extends State<LinePainterAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this);
_controller.forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedBuilder(
animation: _controller,
builder: (context, child) => CustomPaint(
painter: LinePainter(_controller.value),
child: Container(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.reset();
_controller.forward();
},
child: const Text('Play'),
),
);
}
}
class LinePainter extends CustomPainter {
final double percent;
LinePainter(this.percent);
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.red
..strokeWidth = 20
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
var path = getPath();
PathMetrics pathMetrics = path.computeMetrics();
PathMetric pathMetric = pathMetrics.elementAt(0);
Path extracted = pathMetric.extractPath(0.0, pathMetric.length * percent);
canvas.drawPath(extracted, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
Path getPath() {
return Path()
..lineTo(50, 300)
..lineTo(150, 100)
..lineTo(300, 300)
..lineTo(200, 300);
}
[1]: https://api.flutter.dev/flutter/dart-ui/PathMetrics-class.html
[2]: https://stackoverflow.com/questions/60203515/moving-along-a-curved-path-in-flutter
[3]: https://medium.com/flutter-community/playing-with-paths-in-flutter-97198ba046c8
[4]: https://i.stack.imgur.com/ayoHn.gif
i want to add heart beat horizontal line for splash screen, it will start animating from one side and go all the way to other if the initial data is still not loaded it will continue animation
i tried below code but its only pulse kinda animation
import 'dart:math';
import 'package:flutter/material.dart';
class SpritePainter extends CustomPainter {
final Animation<double> _animation;
SpritePainter(this._animation) : super(repaint: _animation);
void circle(Canvas canvas, Rect rect, double value) {
double opacity = (1.0 - (value / 4.0)).clamp(0.0, 1.0);
Color color = Color.fromRGBO(0, 117, 194, opacity);
double size = rect.width / 2;
double area = size * size;
double radius = sqrt(area * value / 4);
final Paint paint = Paint()..color = color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(SpritePainter oldDelegate) {
return true;
}
}
class SpriteDemo extends StatefulWidget {
#override
SpriteDemoState createState() => SpriteDemoState();
}
class SpriteDemoState extends State<SpriteDemo>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
);
//_startAnimation();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startAnimation() {
_controller
..stop()
..reset()
..repeat(period: const Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pulse')),
body: CustomPaint(
painter: SpritePainter(_controller),
child: SizedBox(
width: 200.0,
height: 200.0,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
}
void main() {
runApp(
MaterialApp(
home: SpriteDemo(),
),
);
}
I want it to start(animate) from center left and go all the way to right just like in image
I want to animate the line drawing in custom painter canvas. So far what I can do is create two circles at two points and then create a line between those two points. But I don't know how to animate the line as if it is going from one point to the other. I have tried something but I can't make it work. Please check the code and suggest me if you have any idea.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class ProgressMonitorAnimation extends StatefulWidget {
#override
State<StatefulWidget> createState() => _ProgressMonitorAnimationState();
}
class _ProgressMonitorAnimationState extends State<ProgressMonitorAnimation> with TickerProviderStateMixin {
double _progress = 0.0;
Animation<double> animation;
#override
void initState() {
var controller = AnimationController(duration: Duration(milliseconds: 3000), vsync: this);
animation = Tween(begin: 1.0, end: 0.0).animate(controller)..addListener(() {
setState(() {
_progress = animation.value;
});
});
controller.forward();
super.initState();
}
#override
Widget build(BuildContext context) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.rotationX(math.pi),
child: CustomPaint(
foregroundPainter: ProgressPainter(_progress),
),
);
}
}
class ProgressPainter extends CustomPainter {
double _progress;
ProgressPainter(this._progress);
#override
void paint(Canvas canvas, Size size) {
final Paint circlePainter = Paint()..color = Colors.green;
final Paint linePainter = Paint()..color = Colors.black..strokeWidth = 4..strokeCap = StrokeCap.round;
canvas.drawCircle(Offset(0.0, 30.0 * 3), 10.0, circlePainter);
canvas.drawCircle(Offset(15.0 * 2, 80.0 * 3), 10.0, circlePainter);
canvas.drawLine(Offset(0.0 / (_progress * 10), 30.0 * 3), Offset((30.0 * 3) + (15.0) / (_progress * 15) * 2, (80.0 * 3) / (_progress * 15)), linePainter);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
You can do as follows using flutter Custom Painter.
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class AnimatedPathPainter extends CustomPainter {
final Animation<double> _animation;
AnimatedPathPainter(this._animation) : super(repaint: _animation);
Path _createAnyPath(Size size) {
return Path()
// ..moveTo(size.height / 4, size.height / 4)
// ..lineTo(size.height, size.width / 2)
// ..lineTo(size.height / 2, size.width)
..quadraticBezierTo(size.height / 2, 100, size.width, size.height);
}
#override
void paint(Canvas canvas, Size size) {
final animationPercent = this._animation.value;
print("Painting + ${animationPercent} - ${size}");
final path = createAnimatedPath(_createAnyPath(size), animationPercent);
final Paint paint = Paint();
paint.color = Colors.black;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 4.0;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Path createAnimatedPath(
Path originalPath,
double animationPercent,
) {
// ComputeMetrics can only be iterated once!
final totalLength = originalPath
.computeMetrics()
.fold(0.0, (double prev, PathMetric metric) => prev + metric.length);
final currentLength = totalLength * animationPercent;
return extractPathUntilLength(originalPath, currentLength);
}
Path extractPathUntilLength(
Path originalPath,
double length,
) {
var currentLength = 0.0;
final path = new Path();
var metricsIterator = originalPath.computeMetrics().iterator;
while (metricsIterator.moveNext()) {
var metric = metricsIterator.current;
var nextLength = currentLength + metric.length;
final isLastSegment = nextLength > length;
if (isLastSegment) {
final remainingLength = length - currentLength;
final pathSegment = metric.extractPath(0.0, remainingLength);
path.addPath(pathSegment, Offset.zero);
break;
} else {
// There might be a more efficient way of extracting an entire path
final pathSegment = metric.extractPath(0.0, metric.length);
path.addPath(pathSegment, Offset.zero);
}
currentLength = nextLength;
}
return path;
}
class AnimatedPathDemo extends StatefulWidget {
#override
_AnimatedPathDemoState createState() => _AnimatedPathDemoState();
}
class _AnimatedPathDemoState extends State<AnimatedPathDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Completer<GoogleMapController> _controllerMap = Completer();
static final CameraPosition _initialPosition = CameraPosition(
// target: LatLng(12.947437, 77.685345),
target: LatLng(7.8731, 80.7718),
zoom: 8,
);
void _startAnimation() {
_controller.stop();
_controller.reset();
_controller.repeat(
period: Duration(seconds: 2),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: const Text('Animated Paint')),
body: Stack(
children: <Widget>[
GoogleMap(
// rotateGesturesEnabled: false,
mapType: MapType.normal,
compassEnabled: false,
initialCameraPosition: _initialPosition,
// polylines: _polylines,
// markers: _markers,
onMapCreated: (GoogleMapController controller) {
// controller.setMapStyle(Utils.mapStyles);
_controllerMap.complete(controller);
},
),
SizedBox(
height: 300,
width: 300,
child: new CustomPaint(
painter: new AnimatedPathPainter(_controller),
),
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
i am trying to achieved some thing like this in flutter
One way is with CustomPainter and an animation. Also look at SpriteWidget.
import 'dart:math';
import 'package:flutter/material.dart';
class SpritePainter extends CustomPainter {
final Animation<double> _animation;
SpritePainter(this._animation) : super(repaint: _animation);
void circle(Canvas canvas, Rect rect, double value) {
double opacity = (1.0 - (value / 4.0)).clamp(0.0, 1.0);
Color color = Color.fromRGBO(0, 117, 194, opacity);
double size = rect.width / 2;
double area = size * size;
double radius = sqrt(area * value / 4);
final Paint paint = Paint()..color = color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(SpritePainter oldDelegate) {
return true;
}
}
class SpriteDemo extends StatefulWidget {
#override
SpriteDemoState createState() => SpriteDemoState();
}
class SpriteDemoState extends State<SpriteDemo>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
);
//_startAnimation();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startAnimation() {
_controller
..stop()
..reset()
..repeat(period: const Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pulse')),
body: CustomPaint(
painter: SpritePainter(_controller),
child: SizedBox(
width: 200.0,
height: 200.0,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
}
void main() {
runApp(
MaterialApp(
home: SpriteDemo(),
),
);
}
You can also use Flare with: flare_flutter. It's more simple.
For those looking to create an animation which is not circular, but rectangular with possible rounded borders, you can replace the SpritePainter from the top answer with:
class SpritePainter extends CustomPainter {
final Animation<double> _animation;
SpritePainter(this._animation) : super(repaint: _animation);
void roundedRect(Canvas canvas, Rect rect, double animValue, int waveAmount) {
double opacity = (1.0 - (animValue / waveAmount)).clamp(0.0, 1.0);
Color color = new Color.fromRGBO(0, 117, 194, opacity);
final pixelMiltiplier = 20;
final newWidth = rect.width + animValue*pixelMiltiplier;
final newHeight = rect.height + animValue*pixelMiltiplier;
final widthIncrease = newWidth/rect.width;
final heightIncrease = newHeight/rect.height;
final widthOffset = (widthIncrease - 1) / 2;
final heightOffet = (heightIncrease - 1) / 2;
final Paint paint = new Paint()..color = color;
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(-rect.width * widthOffset, -rect.height * heightOffet,
rect.width * widthIncrease, rect.height * heightIncrease),
Radius.circular(15.0)),
paint);
}
#override
void paint(Canvas canvas, Size size) {
Rect rect = new Rect.fromLTRB(0.0, 0.0, size.width, size.height);
final waveAmount = 1;
if (!_animation.isDismissed) {
for (int wave = waveAmount-1; wave >= 0; wave--) {
roundedRect(canvas, rect, wave + _animation.value, waveAmount);
}
}
}
#override
bool shouldRepaint(SpritePainter oldDelegate) {
return true;
}
}
I am animating line in canvas in flutter.i am using AnimationController to control the animation. When i animate a single line, it is get animated without any lag or performance issue.But when i animate more than 10 lines it gets struck and lagging when rendering the line.In each frame redraw is getting called in order to animate the line .How to overcome this issue.
Code Snippet
class CrossPainter extends CustomPainter {
Paint _paint;
double _fraction;
CrossPainter(this._fraction) {
_paint = Paint()
..color = Colors.blue
..strokeWidth = 10.0
..strokeCap = StrokeCap.round;
}
#override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Rect.fromLTRB(0, 0, _fraction * size.width , size.height));
canvas.drawLine(Offset(0.0, 0.0), Offset(size.width , size.height ), _paint);
canvas.drawLine(Offset(size.width, 0.0), Offset(size.width - size.width, size.height ), _paint);
}
#override
bool shouldRepaint(CrossPainter oldDelegate) {
return oldDelegate._fraction != _fraction;
}
}
typedef FadeBuilder = Widget Function(BuildContext, double);
class _AnimationWrapper extends StatefulWidget {
const _AnimationWrapper({this.builder});
final FadeBuilder builder;
#override
_AnimationWrapperState createState() => _AnimationWrapperState();
}
class _AnimationWrapperState extends State<_AnimationWrapper> with SingleTickerProviderStateMixin {
double opacity = 0.0;
double _fraction = 0.0;
Animation<double> animation;
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(milliseconds: 3000), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {
_fraction = animation.value;
});
}
);
controller.forward();
}
#override void didUpdateWidget(_AnimationWrapper oldWidget) {
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: CrossPainter(_fraction));
}
}
Thanks
Ashwin
If I understand your problem correct - these lags caused by debug mode. There are always small problems with animation in debug.
Try to build release apk and launch it