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;
}
}
Related
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
In my flutter application I want to animate the color change (the color is a gradient) of two elements that inherit from a custom painter, this happens when a button is clicked.
My problem is that I can't find how to do it, since I understand that to animate an element a Tween is needed.
I cannot find a Tween that is correct for my case, since the color change is done in the CustomPainter conditionally.
The TweenColor class works, however I can't integrate it with a gradient and this doesn't work for me.
This effect is the one I want:
https://vimeo.com/578534813/e0f2d4ed73
This is what I have without using the Tween Color, it is fine, however, it is not the animated effect that I want.
https://vimeo.com/578538292/6e63a51060
As you can see, it is close to what is required, but it is not.
Thanks for your help, I leave the code.
ScreenClass
class AuthScreen extends StatefulWidget {
#override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> with SingleTickerProviderStateMixin
{
late AnimationController controller;
late Animation<double> opacity;
#override
void initState() {
controller = new AnimationController(vsync: this,duration: Duration(seconds:2 ));
opacity = Tween(begin: 0.1 ,end: 1.0).animate(controller);
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
controller.forward();
final authProvider = Provider.of<AuthProvider>(context,listen: false);
return Scaffold(
body: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
child: AnimatedBuilder(
animation: controller,
builder: (_,Widget? child){
return Opacity(
opacity: opacity.value,
child: CustomPaint(
painter: BackgroundPainter(provider:authProvider),
),
);
},
),
),
Center(
child: MaterialButton(
onPressed: (){
controller.reset();
controller.forward();
authProvider.flag = !authProvider.flag;
},
shape: RoundedRectangleBorder(),
elevation: 12,
color: Colors.white,
child: Text('Ingresar'),
),
),
])
);
}
}
ProviderClass
class AuthProvider extends ChangeNotifier {
bool _flag = false;
bool get flag {
print(_flag);
return this._flag;
}
set flag(bool state) => this._flag = state;
}
CustomPaintClass
class BackgroundPainter extends CustomPainter {
AuthProvider provider;
BackgroundPainter({required this.provider});
final Gradient redColor = new LinearGradient(colors: <Color>[
Colors.white12,
Color(0xffF7A2A3),
]);
final Gradient blueColor = new LinearGradient(colors: <Color>[
Colors.white12,
Color(0xff74BDD7),
]);
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromCircle(
center: Offset(55.0, 155.0),
radius: 180);
/**
* *Header*
*/
Gradient color ;
if(!provider.flag)
color = redColor;
else
color = blueColor;
final paint = new Paint()..shader = color.createShader(rect);
paint.style = PaintingStyle.fill; // .fill .stroke
paint.strokeWidth = 20;
final path = new Path();
path.moveTo(size.width, 0);
path.lineTo(size.width, size.height * 0.20);
path.quadraticBezierTo(
size.width * 0.95, size.height * 0.05, 0, size.height * 0.06);
path.lineTo(0, 0);
canvas.drawPath(path, paint);
final Rect rect2 = Rect.fromCircle(
center: Offset(55.0, 155.0), // x = horizontal y = vertical
radius: 180);
/**
* *Footer*
*/
Gradient color2 ;
if(!provider.flag)
color2 = blueColor;
else
color2 = redColor;
final paint2 = new Paint()..shader = color2.createShader(rect2);
paint2.style = PaintingStyle.fill;
paint2.strokeWidth = 30;
final path2 = new Path();
path2.moveTo(0, size.height);
path2.lineTo(0, size.height * 0.80);
path2.quadraticBezierTo(
size.width * 0.05, size.height * 0.95, size.width, size.height * 0.94);
path2.lineTo(size.width, size.height);
canvas.drawPath(path2, paint2);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
How do you draw a diagram style circle border with multiple values? Also animated that each value in circle expands dynamically filling 100% of the circle?
Animation can be handled by TweenAnimationBuilder and it will be played on build.
To achieve desired result we must use customPainter.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TweenAnimationBuilder(
duration: const Duration(seconds: 2),
tween: Tween(begin: 0.0, end: 1.0),
curve: Curves.easeOutCubic,
builder: (BuildContext context, dynamic value, Widget child) {
return CustomPaint(
painter: OpenPainter(
totalQuestions: 300,
learned: 75,
notLearned: 75,
range: value),
);
},
),
),
);
}
}
class OpenPainter extends CustomPainter {
final learned;
final notLearned;
final range;
final totalQuestions;
double pi = math.pi;
OpenPainter({this.learned, this.totalQuestions, this.notLearned, this.range});
#override
void paint(Canvas canvas, Size size) {
double strokeWidth = 7;
Rect myRect = const Offset(-50.0, -50.0) & const Size(100.0, 100.0);
var paint1 = Paint()
..color = Colors.red
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
var paint2 = Paint()
..color = Colors.green
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
var paint3 = Paint()
..color = Colors.yellow
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
double firstLineRadianStart = 0;
double _unAnswered = (totalQuestions - notLearned - learned) * range / totalQuestions;
double firstLineRadianEnd = (360 * _unAnswered) * math.pi / 180;
canvas.drawArc(
myRect, firstLineRadianStart, firstLineRadianEnd, false, paint1);
double _learned = (learned) * range / totalQuestions;
double secondLineRadianEnd = getRadians(_learned);
canvas.drawArc(myRect, firstLineRadianEnd, secondLineRadianEnd, false, paint2);
double _notLearned = (notLearned) * range / totalQuestions;
double thirdLineRadianEnd = getRadians(_notLearned);
canvas.drawArc(myRect, firstLineRadianEnd + secondLineRadianEnd, thirdLineRadianEnd, false, paint3);
// drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)
}
double getRadians(double value) {
return (360 * value) * pi / 180;
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Hopefully someone will find this helpfull :) Feel free to improve on this! Happy coding !
Thanks to Paulius Greičiūnas answer, I implemented a more general way to paint a circle in different colors. You only have to specify the occurrences of the colors as a map and a size of the circle.
class MultipleColorCircle extends StatelessWidget {
final Map<Color, int> colorOccurrences;
final double height;
final Widget? child;
#override
MultipleColorCircle(
{required this.colorOccurrences, this.height = 20, this.child});
Widget build(BuildContext context) => Container(
height: height,
width: height,
child: CustomPaint(
size: Size(20, 20),
child: Center(child: child),
painter: _MultipleColorCirclePainter(
colorOccurrences: colorOccurrences,
height: height,
)),
);
}
class _MultipleColorCirclePainter extends CustomPainter {
final Map<Color, int> colorOccurrences;
final double height;
#override
_MultipleColorCirclePainter(
{required this.colorOccurrences, required this.height});
double pi = math.pi;
#override
void paint(Canvas canvas, Size size) {
double strokeWidth = 1;
Rect myRect =
Rect.fromCircle(center: Offset(height / 2, height / 2), radius: height);
double radianStart = 0;
double radianLength = 0;
int allOccurrences = 0;
//set denominator
colorOccurrences.forEach((color, occurrence) {
allOccurrences += occurrence;
});
colorOccurrences.forEach((color, occurrence) {
double percent = occurrence / allOccurrences;
radianLength = 2 * percent * math.pi;
canvas.drawArc(
myRect,
radianStart,
radianLength,
false,
Paint()
..color = color
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke);
radianStart += radianLength;
});
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
With a map e.g. {Colors.blue: 2, Colors.green: 1} you will get a circle with 1/3 green and 2/3 blue.
Note, that you can also define a child, so that the circle has content in it. Here is an example, which I used in my calendar, of multiple circles with content in it.
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'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;
}