Flutter _debugLifecycleState != _ElementLifecycle.defunct': is not true - flutter

I am showing some animated custom painter progress bar in my app it's showing some error
Error: The following assertion was thrown while notifying listeners for AnimationController:
'package:flutter/src/widgets/framework.dart': Failed assertion: line 4263 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is not true.
I have a simple homePage in which I have a navigation bar. I am simply showing the container when navigation change like this
SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/sidebg.png"),
fit: BoxFit.cover,
),
),
child: Column(
children: [
pageIndex == 0 ? DashboardScreen() : Container(),
pageIndex == 1 ? MapScreen() : Container(),
pageIndex == 3 ? ServiceCenter() : Container(),
pageIndex == 4 ? ProfileScreen() : Container(),
],
)),
),
Issue is as you can see pages changes when index are changing but when i change the page its showing an error as i mention above in continuesly loop not stopping.
If I remove this progress indicator all is working fine.
This is the progress indicator screen
import 'dart:math' as math;
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:sleek_circular_slider/sleek_circular_slider.dart';
import 'package:liquid_progress_indicator/liquid_progress_indicator.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:smooth_star_rating/smooth_star_rating.dart';
class DashboardScreen extends StatefulWidget {
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Column(
children: [
SizedBox(
height: height * 0.05,
),
Circular_arc(),
],
),
);
}
}
final Gradient gradient = new LinearGradient(
colors: <Color>[
Colors.greenAccent.withOpacity(1.0),
Colors.yellowAccent.withOpacity(1.0),
Colors.redAccent.withOpacity(1.0),
],
stops: [
0.0,
0.5,
1.0,
],
);
class Circular_arc extends StatefulWidget {
const Circular_arc({
Key key,
}) : super(key: key);
#override
_Circular_arcState createState() => _Circular_arcState();
}
class _Circular_arcState extends State<Circular_arc>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController animController;
#override
void initState() {
// TODO: implement initState
super.initState();
animController =
AnimationController(duration: Duration(seconds: 3), vsync: this);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeInOutCubic);
animation = Tween<double>(begin: 0.0, end: 2).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
animController.repeat(max: 1);
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Stack(
children: [
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(null, Colors.black54, true),
),
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(animation.value, Colors.redAccent, false),
),
Positioned(
top: height * 0.07,
left: width * 0.2,
child: Column(
children: [
Image.asset(
'images/star-icon-fill#3x.png',
height: height * 0.045,
),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: '4.6',
style: new TextStyle(
fontSize: 40, fontFamily: 'UbuntuRegular')),
new TextSpan(
text: ' /5',
style: TextStyle(
fontSize: 25,
color: Colors.grey[400],
fontFamily: 'UbuntuRegular')),
],
),
),
Text(
'FIFTEEN DAYS SCORE',
style: TextStyle(
color: Colors.grey[400], fontFamily: 'UbuntuMedium'),
)
],
),
)
],
),
);
}
}
class ProgressArc extends CustomPainter {
bool isBackground;
double arc;
Color progressColor;
ProgressArc(this.arc, this.progressColor, this.isBackground);
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(0, 0, 300, 300);
final startAngle = -math.pi;
final sweepAngle = arc != null ? arc : math.pi;
final userCenter = false;
final paint = Paint()
..strokeCap = StrokeCap.round
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = 10;
if (!isBackground) {
paint.shader = gradient.createShader(rect);
}
canvas.drawArc(rect, startAngle, sweepAngle, userCenter, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
Error is in the continuous loop it's not stopping. And also its starts when I change the page index (mean when I change the navigation from home).

In your _CircularArcState, please...
Call animController.forward(); after animController.repeat(max: 1);
To save the state of CircularArc, add mixin AutomaticKeepAliveClientMixin in _CircularArcState. Then override wantKeepAlive and return true. Also, call super.build(context); inside build(...).
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PageView(
scrollDirection: Axis.vertical,
children: [
DashboardScreen(),
Container(color: Colors.orange),
Container(color: Colors.blue),
Container(color: Colors.green),
],
),
),
);
}
}
class DashboardScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Column(
children: [
SizedBox(
height: height * 0.05,
),
CircularArc(),
],
),
);
}
}
final Gradient gradient = new LinearGradient(
colors: <Color>[
Colors.greenAccent.withOpacity(1.0),
Colors.yellowAccent.withOpacity(1.0),
Colors.redAccent.withOpacity(1.0),
],
stops: [0.0, 0.5, 1.0],
);
class CircularArc extends StatefulWidget {
const CircularArc({
Key key,
}) : super(key: key);
#override
_CircularArcState createState() => _CircularArcState();
}
class _CircularArcState extends State<CircularArc>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
double width;
double height;
Animation<double> animation;
#override
void initState() {
super.initState();
final AnimationController animController =
AnimationController(duration: Duration(seconds: 3), vsync: this);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeInOutCubic);
animation = Tween<double>(begin: 0.0, end: 2).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
animController.repeat(max: 1);
animController.forward();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
if (width == null && height == null) {
width = MediaQuery.of(context).size.width;
height = MediaQuery.of(context).size.height;
}
return Container(
child: Stack(
children: [
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(null, Colors.black54, true),
),
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(animation.value, Colors.redAccent, false),
),
Positioned(
top: height * 0.07,
left: width * 0.2,
child: Column(
children: [
// Image.asset(
// 'images/star-icon-fill#3x.png',
// height: height * 0.045,
// ),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: '4.6',
style: new TextStyle(
fontSize: 40, fontFamily: 'UbuntuRegular')),
new TextSpan(
text: ' /5',
style: TextStyle(
fontSize: 25,
color: Colors.grey[400],
fontFamily: 'UbuntuRegular')),
],
),
),
Text(
'FIFTEEN DAYS SCORE',
style: TextStyle(
color: Colors.grey[400], fontFamily: 'UbuntuMedium'),
)
],
),
)
],
),
);
}
}
class ProgressArc extends CustomPainter {
bool isBackground;
double arc;
Color progressColor;
ProgressArc(this.arc, this.progressColor, this.isBackground);
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(0, 0, 300, 300);
final startAngle = -math.pi;
final sweepAngle = arc != null ? arc : math.pi;
final userCenter = false;
final paint = Paint()
..strokeCap = StrokeCap.round
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = 10;
if (!isBackground) {
paint.shader = gradient.createShader(rect);
}
canvas.drawArc(rect, startAngle, sweepAngle, userCenter, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}

first dispose the controller and then use super.dispose()
void dispose() { controller.dispose(); super.dispose(); }

Related

Flutter confetti issue

At the end of liquid swipe, when the Let's start button is clicked, I would like confetti animation to play for 3-4 seconds and then present the user with App home screen. When I run this code, I don't get any error and neither does confetti animation play. On clicking Let's start, the home screen of app is displayed.
Update: 25/11/2022 I was able to run confetti successfully and have also added a listener to capture current state. I would like the confetti to run for 3 seconds and the screen to be routed to home screen using
AppRoute.pushReplacement(context, const MyApp());
import 'package:a/main.dart';
import 'package:a/provider/home_provider.dart';
import 'package:a/provider/user_provider.dart';
import 'package:a/ui/app_route.dart';
import 'package:a/ui/widget/styled_text.dart';
import 'package:a/utils/app_colors.dart';
import 'package:flutter/material.dart';
import 'package:liquid_swipe/liquid_swipe.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:confetti/confetti.dart';
class OnBoarding extends StatefulWidget {
const OnBoarding({Key? key}) : super(key: key);
#override
_OnBoardingState createState() => _OnBoardingState();
}
class _OnBoardingState extends State<OnBoarding> {
#override
Widget build(BuildContext context) {
final pages = [
IntroductionContainer(
heading:
'a',
body:
"b",
color: Color(0xff3937bf),
animation: 'assets/lottie/a.json',
),
IntroductionContainer(
heading:
'c?',
body:
"d",
color: Colors.pink,
animation: 'assets/lottie/h.json',
),
IntroductionContainer(
heading:
'e',
body:
"f",
color: Color(0xff27b56f),
animation: 'assets/lottie/g.json',
),
IntroductionContainer(
showAction: true,
heading: 'Ready ?',
body:
"aaaa",
color: Color(0xfff46d37),
),
];
return Scaffold(
body: LiquidSwipe(
pages: pages,
enableSideReveal: true,
// enableSlideIcon: true,
enableLoop: false,
positionSlideIcon: 0,
slideIconWidget: const Icon(
Icons.arrow_back_ios,
size: 30,
color: Colors.white,
),
),
);
}
}
class IntroductionContainer extends StatefulWidget {
IntroductionContainer(
{required this.heading,
required this.body,
required this.color,
this.showAction = false,
this.animation,
Key? key})
: super(key: key);
final String heading;
final String body;
final Color color;
final bool showAction;
final String? animation;
#override
State<IntroductionContainer> createState() => _IntroductionContainerState();
}
class _IntroductionContainerState extends State<IntroductionContainer> {
//Adding Confetti controller and other variables
bool isPlaying = false;
final _controller = ConfettiController(duration: const Duration(seconds: 2));
#override
void initState() {
// TODO: implement initState
super.initState();
//Listen to states playing, stopped
_controller.addListener(() {
setState(() {
isPlaying = _controller.state == ConfettiControllerState.playing;
});
if (_controller.state == ConfettiControllerState.stopped) {
AppRoute.pushReplacement(context, const MyApp());
}
});
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
color: widget.color,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
widget.heading,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 25,
),
),
const SizedBox(height: 10),
if (widget.animation != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Lottie.asset(
widget.animation!,
height: 400,
width: 400,
fit: BoxFit.fill,
),
),
if (widget.animation == null)
InkWell(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Stack(
alignment: Alignment.center,
children: [
Container(
height: 200,
width: 200,
alignment: Alignment.center,
child: Image.asset(
'assets/a-2.png',
height: 120,
width: 120,
fit: BoxFit.contain,
),
),
const Icon(
Icons.play_circle,
size: 60,
)
],
),
),
onTap: () async {
var url = 'https://www.youtube.com/watch?v=aaaaaa';
if (await canLaunchUrlString(url)) {
launchUrlString(url);
}
},
),
Text(
widget.body,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
),
),
ConfettiWidget(
confettiController: _controller,
//set direction
//blastDirectionality: BlastDirectionality.explosive,
// to set direction of confetti upwards
blastDirection: -3.14 / 2,
//minBlastForce: 10,
//maxBlastForce: 100,
//colors: const [Colors.deepPurple, Colors.black, Colors.yellow],
numberOfParticles: 20,
gravity: 0.5,
emissionFrequency: 0.3,
// - Path to create oval particles
createParticlePath: (size) {
final path = Path();
path.addOval(Rect.fromCircle(
center: Offset.zero,
radius: 4,
));
return path;
},
// Path to create star confetti
/// A custom Path to paint stars.
//Path drawStar(Size size) {
// Method to convert degree to radians
//double degToRad(double deg) => deg * (pi / 180.0);
//const numberOfPoints = 5;
//final halfWidth = size.width / 2;
//final externalRadius = halfWidth;
//final internalRadius = halfWidth / 2.5;
//final degreesPerStep = degToRad(360 / numberOfPoints);
//final halfDegreesPerStep = degreesPerStep / 2;
//final path = Path();
//final fullAngle = degToRad(360);
//path.moveTo(size.width, halfWidth);
//for (double step = 0; step < fullAngle; step += degreesPerStep) {
//path.lineTo(halfWidth + externalRadius * cos(step),
// halfWidth + externalRadius * sin(step));
//path.lineTo(halfWidth + internalRadius * cos(step + halfDegreesPerStep),
// halfWidth + internalRadius * sin(step + halfDegreesPerStep));
// }
//path.close();
//return path;
//}
),
//ConfettiWidget(
const SizedBox(height: 50),
if (widget.showAction)
ButtonTheme(
height: 50,
minWidth: 150,
child: MaterialButton(
// borderSide: BorderSide(color: Colors.white),
color: AppColors.blue,
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: const Text(
'Let\'s Go',
style: TextStyle(fontSize: 20),
),
onPressed: () async {
//Calling Confetti controller and adding confetti widget
if (isPlaying) {
_controller.stop();
//await Duration(seconds: 4);
AppRoute.pushReplacement(context, const MyApp());
} else {
_controller.play();
//await Duration(seconds: 1);
}
//await Duration(seconds: 1);
//confettiController: confettiController,
// shouldLoop: false,
// blastDirectionality: BlastDirectionality.explosive,
//);
//var userProvider =
//Provider.of<UserProvider>(context, listen: false);
//userProvider.updateTrial();
// HomeProvider.setAppLaunched();
//AppRoute.pushReplacement(context, const MyApp());
//},
}),
),
],
),
),
);
}
}
At the end of liquid swipe, when the Let's start button is clicked, I would like confetti animation to play for 3-4 seconds and then present the user with App home screen. When I run this code, I don't get any error and neither does confetti animation play. On clicking Let's start, the home screen of app is displayed.
PS: I am new to flutter
Your mistake is that you put the ConfettiWidget inside of the onPressed callback of your button.
You should add it in your Column like any other widget and display the confetti by triggering the controller you pass to your ConfettiWidget inside the onPressed callback with the help of _controller.play()

Quarter Circle does not fit my initial circle Flutter Painter

I'm trying to make a loading icon for my loading screen using CustomPaint widget. Unfortunally, it doesn't work as expected...
My quarter circle does not fit in my primary circle, here's a screen of it :
I don't understand why it is doing this...
Here's the code:
import 'package:flutter/material.dart';
import 'package:responsive_sizer/responsive_sizer.dart';
import 'dart:math' as math;
class LoadingPage extends StatelessWidget {
const LoadingPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 25.h,
),
Stack(
alignment: Alignment.center,
children: [
Container(
width: 28.w,
height: 28.w,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(width: 3.w, color: Colors.white.withOpacity(0.2)),
),
),
CustomPaint(
painter: MyPainter(),
size: Size(28.w, 28.w),
),
]
),
SizedBox(
height: 6.h,
),
Text(
"LOADING",
style: Theme.of(context).textTheme.headline2,
)
],
);
}
}
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..strokeWidth = 3.w
..style = PaintingStyle.stroke
..color = Colors.blue;
double degToRad(double deg) => deg * (math.pi / 180.0);
final path = Path()
..arcTo(
Rect.fromCircle(
center: Offset(size.width/2, size.height/2),
radius: 28.w/2,
),
degToRad(90),
degToRad(90),
false);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Does anyone knows why ? :)
Thanks for your answers,
Chris

How to create a border like this in Flutter?

How to create a border like this:
You can achieve a similar clipped border using CustomClipper. Here is a simple CustomClipper I have created for you.
First Create a custom clipper.
class MyClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var smallLineLength = size.width / 20;
const smallLineHeight = 20;
var path = Path();
path.lineTo(0, size.height);
for (int i = 1; i <= 20; i++) {
if (i % 2 == 0) {
path.lineTo(smallLineLength * i, size.height);
} else {
path.lineTo(smallLineLength * i, size.height - smallLineHeight);
}
}
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper old) => false;
}
And wrap the created CustomClipper with ClipPath.
SizedBox(
height: 200,
width: 500,
child: ClipPath(
clipper: MyClipper(),
child: Container(
height: 200,
width: 500,
alignment: Alignment.center,
color: Colors.red,
child: const Text("abc"),
)),
),
Full Code:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
SizedBox(
height: 200,
width: 500,
child: ClipPath(
clipper: MyClipper(),
child: Container(
height: 200,
width: 500,
alignment: Alignment.center,
color: Colors.red,
child: const Text("abc"),
)),
),
]),
);
}
}
class MyClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var smallLineLength = size.width / 20;
const smallLineHeight = 20;
var path = Path();
path.lineTo(0, size.height);
for (int i = 1; i <= 20; i++) {
if (i % 2 == 0) {
path.lineTo(smallLineLength * i, size.height);
} else {
path.lineTo(smallLineLength * i, size.height - smallLineHeight);
}
}
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper old) => false;
}
You can run this code by copying/pasting in dartpad.
You can learn more about CustomClipper from here, medium article
Try below code hope its helpful to you. Used flutter_custom_clippers package here and used PointsClipper() for your expected design In this package you used more shapes see documentation in given link. add flutter_custom_clippers: ^2.0.0 dependency in your pubspec.yaml file
ClipPath(
clipper: PointsClipper(),
child: Container(
height: 100,
color: Colors.grey[300],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Total',
style: TextStyle(
color: Colors.pink,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
'QAR 130.00',
style: TextStyle(
color: Colors.pink,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
Your result screen->
import 'package:flutter/material.dart';
import 'a.dart';
void main() => runApp(VideoPlayerApp());
class VideoPlayerApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Zigzag',
home: SafeArea(
child: Scaffold(
body: ZigzagApp(),
),
),
);
}
}
class ZigzagApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: 200,
color: Colors.pinkAccent,
child: CustomPaint(
size: MediaQuery.of(context).size,
painter: MyPainter(),
),
);
}
}
//paint widget for zigzag
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.white;
paint.style = PaintingStyle.fill;
paintZigZag(canvas, paint, Offset(0, 200), Offset(400, 200), 35, 10);
}
void paintZigZag(Canvas canvas, Paint paint, Offset start, Offset end,
int zigs, double width) {
assert(zigs.isFinite);
assert(zigs > 0);
canvas.save();
canvas.translate(start.dx, start.dy);
end = end - start;
canvas.rotate(math.atan2(end.dy, end.dx));
final double length = end.distance;
final double spacing = length / (zigs * 2.0);
final Path path = Path()..moveTo(0.0, 0.0);
for (int index = 0; index < zigs; index += 1) {
final double x = (index * 2.0 + 1.0) * spacing;
final double y = width * ((index % 2.0) * 2.0 - 1.0);
path.lineTo(x, y);
}
path.lineTo(length, 0.0);
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

how to make Percent Indicator change color programmatically in flutter?

I'm using a package called percent indicator https://pub.dev/packages/percent_indicator
and I'm currently using its CircularPercentIndicator()
I'm just wondering how to change the progress color when a certain percentage met?
For example: I have a starting progress color of green at 0% when reaching 60% progress color should change to orange and when reaching 80% color should be red.
here's what I got at the moment:
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class RoutinePage extends StatefulWidget {
#override
_RoutinePageState createState() => _RoutinePageState();
}
class _RoutinePageState extends State<RoutinePage> {
double progress = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
color: Colors.white,
alignment: Alignment(0, 0),
child: CircularPercentIndicator(
animationDuration: 100,
animateFromLastPercent: true,
arcType: ArcType.FULL,
arcBackgroundColor: Colors.black12,
backgroundColor: Colors.white,
progressColor: Colors.green,
percent: progress,
animation: true,
radius: 250.0,
lineWidth: 12.0,
circularStrokeCap: CircularStrokeCap.round,
),
),
Container(
alignment: Alignment(0, 0),
child: Text("${this.progress * 100}%",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Container(
alignment: Alignment(0.3, 0.5),
child: RaisedButton(
color: Colors.green,
onPressed: () {
final updated = ((this.progress + 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text('+10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),)),
),
Container(
alignment: Alignment(-0.3, 0.5),
child: RaisedButton(
color: Colors.red,
onPressed: () {
final updated = ((this.progress - 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text('-10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),)),
),
],
);
}
}
and I don't know if this will help but this is the code of CircularPercentIndicator()
//import 'dart:math';
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' as math;
enum CircularStrokeCap { butt, round, square }
enum ArcType {
HALF,
FULL,
}
class CircularPercentIndicator extends StatefulWidget {
///Percent value between 0.0 and 1.0
final double percent;
final double radius;
///Width of the line of the Circle
final double lineWidth;
///Color of the background of the circle , default = transparent
final Color fillColor;
///First color applied to the complete circle
final Color backgroundColor;
Color get progressColor => _progressColor;
Color _progressColor;
///true if you want the circle to have animation
final bool animation;
///duration of the animation in milliseconds, It only applies if animation attribute is true
final int animationDuration;
///widget at the top of the circle
final Widget header;
///widget at the bottom of the circle
final Widget footer;
///widget inside the circle
final Widget center;
final LinearGradient linearGradient;
///The kind of finish to place on the end of lines drawn, values supported: butt, round, square
final CircularStrokeCap circularStrokeCap;
///the angle which the circle will start the progress (in degrees, eg: 0.0, 45.0, 90.0)
final double startAngle;
/// set true if you want to animate the linear from the last percent value you set
final bool animateFromLastPercent;
/// set false if you don't want to preserve the state of the widget
final bool addAutomaticKeepAlive;
/// set the arc type
final ArcType arcType;
/// set a circular background color when use the arcType property
final Color arcBackgroundColor;
/// set true when you want to display the progress in reverse mode
final bool reverse;
/// Creates a mask filter that takes the progress shape being drawn and blurs it.
final MaskFilter maskFilter;
CircularPercentIndicator(
{Key key,
this.percent = 0.0,
this.lineWidth = 5.0,
this.startAngle = 0.0,
#required this.radius,
this.fillColor = Colors.transparent,
this.backgroundColor = const Color(0xFFB8C7CB),
Color progressColor,
this.linearGradient,
this.animation = false,
this.animationDuration = 500,
this.header,
this.footer,
this.center,
this.addAutomaticKeepAlive = true,
this.circularStrokeCap,
this.arcBackgroundColor,
this.arcType,
this.animateFromLastPercent = false,
this.reverse = false,
this.maskFilter})
: super(key: key) {
if (linearGradient != null && progressColor != null) {
throw ArgumentError(
'Cannot provide both linearGradient and progressColor');
}
_progressColor = progressColor ?? Colors.red;
assert(startAngle >= 0.0);
if (percent < 0.0 || percent > 1.0) {
throw Exception("Percent value must be a double between 0.0 and 1.0");
}
if (arcType == null && arcBackgroundColor != null) {
throw ArgumentError('arcType is required when you arcBackgroundColor');
}
}
#override
_CircularPercentIndicatorState createState() =>
_CircularPercentIndicatorState();
}
class _CircularPercentIndicatorState extends State<CircularPercentIndicator>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
AnimationController _animationController;
Animation _animation;
double _percent = 0.0;
#override
void dispose() {
if (_animationController != null) {
_animationController.dispose();
}
super.dispose();
}
#override
void initState() {
if (widget.animation) {
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: widget.animationDuration));
_animation =
Tween(begin: 0.0, end: widget.percent).animate(_animationController)
..addListener(() {
setState(() {
_percent = _animation.value;
});
});
_animationController.forward();
} else {
_updateProgress();
}
super.initState();
}
#override
void didUpdateWidget(CircularPercentIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.percent != widget.percent ||
oldWidget.startAngle != widget.startAngle) {
if (_animationController != null) {
_animationController.duration =
Duration(milliseconds: widget.animationDuration);
_animation = Tween(
begin: widget.animateFromLastPercent ? oldWidget.percent : 0.0,
end: widget.percent)
.animate(_animationController);
_animationController.forward(from: 0.0);
} else {
_updateProgress();
}
}
}
_updateProgress() {
setState(() {
_percent = widget.percent;
});
}
#override
Widget build(BuildContext context) {
super.build(context);
var items = List<Widget>();
if (widget.header != null) {
items.add(widget.header);
}
items.add(Container(
height: widget.radius + widget.lineWidth,
width: widget.radius,
child: CustomPaint(
painter: CirclePainter(
progress: _percent * 360,
progressColor: widget.progressColor,
backgroundColor: widget.backgroundColor,
startAngle: widget.startAngle,
circularStrokeCap: widget.circularStrokeCap,
radius: (widget.radius / 2) - widget.lineWidth / 2,
lineWidth: widget.lineWidth,
arcBackgroundColor: widget.arcBackgroundColor,
arcType: widget.arcType,
reverse: widget.reverse,
linearGradient: widget.linearGradient,
maskFilter: widget.maskFilter),
child: (widget.center != null)
? Center(child: widget.center)
: Container(),
)));
if (widget.footer != null) {
items.add(widget.footer);
}
return Material(
color: widget.fillColor,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: items,
)),
);
}
#override
bool get wantKeepAlive => widget.addAutomaticKeepAlive;
}
class CirclePainter extends CustomPainter {
final Paint _paintBackground = Paint();
final Paint _paintLine = Paint();
final Paint _paintBackgroundStartAngle = Paint();
final double lineWidth;
final double progress;
final double radius;
final Color progressColor;
final Color backgroundColor;
final CircularStrokeCap circularStrokeCap;
final double startAngle;
final LinearGradient linearGradient;
final Color arcBackgroundColor;
final ArcType arcType;
final bool reverse;
final MaskFilter maskFilter;
CirclePainter(
{this.lineWidth,
this.progress,
#required this.radius,
this.progressColor,
this.backgroundColor,
this.startAngle = 0.0,
this.circularStrokeCap = CircularStrokeCap.round,
this.linearGradient,
this.reverse,
this.arcBackgroundColor,
this.arcType,
this.maskFilter}) {
_paintBackground.color = backgroundColor;
_paintBackground.style = PaintingStyle.stroke;
_paintBackground.strokeWidth = lineWidth;
if (arcBackgroundColor != null) {
_paintBackgroundStartAngle.color = arcBackgroundColor;
_paintBackgroundStartAngle.style = PaintingStyle.stroke;
_paintBackgroundStartAngle.strokeWidth = lineWidth;
}
_paintLine.color = progressColor;
_paintLine.style = PaintingStyle.stroke;
_paintLine.strokeWidth = lineWidth;
if (circularStrokeCap == CircularStrokeCap.round) {
_paintLine.strokeCap = StrokeCap.round;
} else if (circularStrokeCap == CircularStrokeCap.butt) {
_paintLine.strokeCap = StrokeCap.butt;
} else {
_paintLine.strokeCap = StrokeCap.square;
}
}
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawCircle(center, radius, _paintBackground);
if (maskFilter != null) {
_paintLine.maskFilter = maskFilter;
}
if (linearGradient != null) {
/*
_paintLine.shader = SweepGradient(
center: FractionalOffset.center,
startAngle: math.radians(-90.0 + startAngle),
endAngle: math.radians(progress),
//tileMode: TileMode.mirror,
colors: linearGradient.colors)
.createShader(
Rect.fromCircle(
center: center,
radius: radius,
),
);*/
_paintLine.shader = linearGradient.createShader(
Rect.fromCircle(
center: center,
radius: radius,
),
);
}
double fixedStartAngle = startAngle;
double startAngleFixedMargin = 1.0;
if (arcType != null) {
if (arcType == ArcType.FULL) {
fixedStartAngle = 220;
startAngleFixedMargin = 172 / fixedStartAngle;
} else {
fixedStartAngle = 270;
startAngleFixedMargin = 135 / fixedStartAngle;
}
}
if (arcBackgroundColor != null) {
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
math.radians(-90.0 + fixedStartAngle),
math.radians(360 * startAngleFixedMargin),
false,
_paintBackgroundStartAngle,
);
}
if (reverse) {
final start =
math.radians(360 * startAngleFixedMargin - 90.0 + fixedStartAngle);
final end = math.radians(-progress * startAngleFixedMargin);
canvas.drawArc(
Rect.fromCircle(
center: center,
radius: radius,
),
start,
end,
false,
_paintLine,
);
} else {
final start = math.radians(-90.0 + fixedStartAngle);
final end = math.radians(progress * startAngleFixedMargin);
canvas.drawArc(
Rect.fromCircle(
center: center,
radius: radius,
),
start,
end,
false,
_paintLine,
);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
One of possible solutions is AnimatedBuilder.
I will show you how can we change color of button and you can easily apply approach to progress indicator.
The example below just shows when tap on button start changing animation. Same for you when you need to start progress bat, just run animationController and check result.
If you have further questions, do not hesitate to ask in comments
#override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_colorTween = ColorTween(begin: Colors.red, end: Colors.green)
.animate(_animationController);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _colorTween,
builder: (context, child) => RaisedButton(
child: Text("Change my color"),
color: _colorTween.value,
onPressed: () {
if (_animationController.status == AnimationStatus.completed) {
_animationController.reverse();
} else {
_animationController.forward();
}
},
),
);
}
thanks for the response. I also came up with other solution and I think I'm good on what I've ended up with. btw here's what I came up with:
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class RoutinePage extends StatefulWidget {
#override
_RoutinePageState createState() => _RoutinePageState();
}
class _RoutinePageState extends State<RoutinePage> {
double progress = 0;
currentProgressColor() {
if (progress >= 0.6 && progress < 0.8) {
return Colors.orange;
}
if(progress >= 0.8){
return Colors.red;
}
else{
return Colors.green;
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
color: Colors.white,
alignment: Alignment(0, 0),
child: CircularPercentIndicator(
animationDuration: 200,
animateFromLastPercent: true,
arcType: ArcType.FULL,
arcBackgroundColor: Colors.black12,
backgroundColor: Colors.white,
progressColor: currentProgressColor(),
percent: progress,
animation: true,
radius: 250.0,
lineWidth: 12.0,
circularStrokeCap: CircularStrokeCap.butt,
),
),
Container(
alignment: Alignment(0, 0),
child: Text(
"${this.progress * 100}%",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Container(
alignment: Alignment(0.3, 0.5),
child: RaisedButton(
color: Colors.green,
onPressed: () {
final updated = ((this.progress + 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text(
'+10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
)),
),
Container(
alignment: Alignment(-0.3, 0.5),
child: RaisedButton(
color: Colors.red,
onPressed: () {
final updated = ((this.progress - 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text(
'-10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
)),
),
],
);
}
}
class ChangeBackgroundColor StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _changeColor(),
initialData: 0xff0DE95B,
builder: (context, snapshot) {
return LinearPercentIndicator(
percent: 1,
animation: true,
animationDuration: 30000,
progressColor: Colors.grey[200],
backgroundColor: Color(
int.parse(snapshot.data.toString()),
)),
);
},
);
}
}
Stream<int> _changeColor() async* {
yield* Stream.periodic(Duration(seconds: 1), (int a) {
a++;
if (a > 25) {
return 0xffF33709;
} else {
return 0xff0DE95B;
}
});
}

Custom Slider in Flutter

I'm having problems to create a custom slider on flutter. The image is the following one:
I know how to create it on XML on Android, but with flutter, I'm having problems. In the Slider class constructor is not a parameter for slider background from assets or a thumb from assets.
Anyone could please help me/ guide me, about how to achieve the expected result? Thanks.
It's possible to create a custom Slider in Flutter. You can visit this guide to learn more. Then you can use RotatedBox if you need the Slider to be vertical.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Custom Slider Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: RotatedBox(quarterTurns: 1, child: SliderWidget()),
),
);
}
}
class CustomSliderThumbCircle extends SliderComponentShape {
final double thumbRadius;
final int min;
final int max;
const CustomSliderThumbCircle({
required this.thumbRadius,
this.min = 0,
this.max = 10,
});
#override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(thumbRadius);
}
#override
void paint(
PaintingContext context,
Offset center, {
Animation<double>? activationAnimation,
Animation<double>? enableAnimation,
bool? isDiscrete,
TextPainter? labelPainter,
RenderBox? parentBox,
required SliderThemeData sliderTheme,
TextDirection? textDirection,
required double value,
double? textScaleFactor,
Size? sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final paint = Paint()
..shader = LinearGradient(
colors: [
const Color(0xFF00c6ff),
const Color(0xFF0072ff),
],
).createShader(Rect.fromCircle(
center: Offset.fromDirection(0.0, 1.0),
radius: 1.0,
));
Paint paintBorder = new Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 3.0;
canvas.drawCircle(center, thumbRadius * .9, paint);
canvas.drawCircle(center, thumbRadius * .9, paintBorder);
}
String getValue(double value) {
return (min + (max - min) * value).round().toString();
}
}
class SliderWidget extends StatefulWidget {
final double sliderHeight;
final int min;
final int max;
final fullWidth;
SliderWidget(
{this.sliderHeight = 48,
this.max = 10,
this.min = 0,
this.fullWidth = false});
#override
_SliderWidgetState createState() => _SliderWidgetState();
}
class _SliderWidgetState extends State<SliderWidget> {
double _value = 0;
#override
Widget build(BuildContext context) {
double paddingFactor = .2;
if (this.widget.fullWidth) paddingFactor = .3;
return Container(
width: this.widget.fullWidth
? double.infinity
: (this.widget.sliderHeight) * 5.5,
height: (this.widget.sliderHeight),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(
Radius.circular((this.widget.sliderHeight * .3)),
),
gradient: new LinearGradient(
colors: [
const Color(0xFF00c6ff),
const Color(0xFF0072ff),
],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 1.00),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
child: Padding(
padding: EdgeInsets.fromLTRB(this.widget.sliderHeight * paddingFactor,
2, this.widget.sliderHeight * paddingFactor, 2),
child: Row(
children: <Widget>[
/// Optional Text for min value
// Text(
// '${this.widget.min}',
// textAlign: TextAlign.center,
// style: TextStyle(
// fontSize: this.widget.sliderHeight * .3,
// fontWeight: FontWeight.w700,
// color: Colors.white,
// ),
// ),
SizedBox(
width: this.widget.sliderHeight * .1,
),
Expanded(
child: Center(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
/// Color of the active track - left of the thumb slider
activeTrackColor: Colors.white.withOpacity(1),
/// Color of the inactive track - right of the thumb slider
inactiveTrackColor: Colors.white.withOpacity(1),
/// Track height
trackHeight: 10.0,
thumbShape: CustomSliderThumbCircle(
thumbRadius: this.widget.sliderHeight * .3,
min: this.widget.min,
max: this.widget.max,
),
overlayColor: Colors.white.withOpacity(.4),
//valueIndicatorColor: Colors.white,
activeTickMarkColor: Colors.white,
inactiveTickMarkColor: Colors.red.withOpacity(.7),
),
child: Slider(
value: _value,
onChanged: (value) {
setState(() {
_value = value;
});
}),
),
),
),
SizedBox(
width: this.widget.sliderHeight * .1,
),
/// Optional Text for max value
// Text(
// '${this.widget.max}',
// textAlign: TextAlign.center,
// style: TextStyle(
// fontSize: this.widget.sliderHeight * .3,
// fontWeight: FontWeight.w700,
// color: Colors.white,
// ),
// ),
],
),
),
);
}
}