Custom Slider in Flutter - 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,
// ),
// ),
],
),
),
);
}
}

Related

I would like to know how to create a slider to change the text size

I would like to know how to create a slider to change the text size.
This is the design.
あ = A
i want increase +0.05
My code is bad . because i can not made same design .
CupertinoSlider(
min: 0.1,
max: 0.6,
value: clockTextSize.toDouble(),
onChanged: (_value) {
setState(
() {
clockTextSize = _value.toInt();
},
);
},
)
i want increase +0.05
you can set division property to achieve this. see documentation
For example, if min is 0.1 and max is 0.6 and divisions is 5, then the slider can take on the values discrete values 0.1, 0.2, 0.3, 0.4, 0.5, and 0.6.
then in your case, if you want increase +0.05, you can set the division=10
CupertinoSlider(
min: 0.1,
max: 0.6,
division:10,
...
result:
Note: your question wasn't too clear if you're looking to achieve the same design or regarding the divisions. This answer is how to achieve the same design.
Result
Customization
To customize the Slider to your liking, you can use SliderTheme.
Then, to customize the Tick, you can create your own class that extends SliderTickMarkShape.
Code
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(debugShowCheckedModeBanner: false, home: Test()));
}
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
var value = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 200,
width: 500,
child: Card(
margin: const EdgeInsets.all(10),
color: const Color(0xff323237),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"あ",
style: TextStyle(color: Colors.grey),
),
Expanded(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
tickMarkShape: CustomTick(),
trackHeight: 10.0,
minThumbSeparation: 2,
thumbColor: Colors.white,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 20,
),
inactiveTickMarkColor: Colors.grey,
inactiveTrackColor: Colors.grey,
activeTrackColor: Colors.grey,
),
child: Slider(
min: 1,
max: 100,
value: value.toDouble(),
divisions: 12,
onChanged: (newValue) {
setState(() {
value = newValue.round();
});
},
),
),
),
const Text(
"あ",
style: TextStyle(color: Colors.grey),
),
],
),
),
),
),
),
);
}
}
class CustomTick extends SliderTickMarkShape {
#override
Size getPreferredSize(
{required SliderThemeData sliderTheme, required bool isEnabled}) {
return const Size(10, 10);
}
#override
void paint(PaintingContext context, Offset center,
{required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required Offset thumbCenter,
required bool isEnabled,
required TextDirection textDirection}) {
final Canvas canvas = context.canvas;
final Paint paint = Paint()..color = Colors.grey;
canvas.drawRect(
Rect.fromCenter(center: center, width: 5, height: 20), paint);
}
}
See also
How can I customize Slider widget in Flutter?

How to create this curves in flutter?

How Can I create this curves and two coloured top "appBar" using Flutter?
The CustomPaint widget will do the trick. With it, it's possible to paint custom shapes in the background like the one you asked for. It's just a matter of using the Stack widget to paint the background first and then the other widgets above it.
This is a prototype of the login screen:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
const loginMainColor = Color.fromARGB(255, 67, 123, 122);
const loginOtherColor = Color.fromARGB(255, 253, 236, 229);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom Shape Widget Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const LoginPage(),
);
}
}
class LoginPage extends StatelessWidget {
const LoginPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: loginMainColor,
),
backgroundColor: Colors.white,
body: Stack(
children: [
Stack(
children: [
CustomPaint(
size: Size(width, height),
painter: const BackgroundPainter(90),
),
Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Log in',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.white),
),
Padding(
padding: EdgeInsets.only(bottom: 60 - 16),
child: Text(
'Lorem ipsum dolor sit amet',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
Padding(
padding: EdgeInsets.only(bottom: 30),
child: TextField(
decoration: InputDecoration(
label: Text('Email'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
),
),
TextField(
decoration: InputDecoration(
label: Text('Password'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
)
],
),
),
],
),
],
),
);
}
}
class BackgroundPainter extends CustomPainter {
final double titleBarHeight;
const BackgroundPainter(this.titleBarHeight);
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = loginMainColor;
const smallRadius = 50.0;
const bigRadius = 120.0;
canvas.drawCircle(
Offset(smallRadius, titleBarHeight - smallRadius), smallRadius, paint);
canvas.drawRect(
Rect.fromPoints(
Offset(0, 0),
Offset(size.width, titleBarHeight - smallRadius),
),
paint,
);
canvas.drawRect(
Rect.fromPoints(
Offset(smallRadius, titleBarHeight - smallRadius),
Offset(size.width, titleBarHeight),
),
paint,
);
paint.color = loginOtherColor;
canvas.drawCircle(
Offset(size.width, titleBarHeight + 60), bigRadius, paint);
paint.color = Colors.white;
canvas.drawCircle(
Offset(size.width - smallRadius, titleBarHeight + smallRadius),
smallRadius,
paint);
canvas.drawRect(
Rect.fromPoints(
Offset(size.width - bigRadius, titleBarHeight),
Offset(size.width - smallRadius, titleBarHeight + 60 + bigRadius),
),
paint);
canvas.drawRect(
Rect.fromPoints(
Offset(size.width - smallRadius, titleBarHeight + smallRadius),
Offset(size.width, titleBarHeight + 60 + bigRadius),
),
paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
As lepsch commented, CustomPaint is the way to go, but it you find it complicated or time-consuming, you can also get some help from tools such as FlutterShapeMaker (FlutterShapeMaker), which allows you to draw your shapes as if you were in some design software and export them into a CustomPainter class
First you edit your shape as you like
Then you export it and the tool generates the required class and imports

What does that mean: "text_painter.dart: Failed assertion: line 900 pos 12: '!_needsLayout': is not true"?

I'm using Flutter 2.5.1 and I'm having this error:
"text_painter.dart: Failed assertion: line 900 pos 12: '!_needsLayout': is not true"
while using the following code (which generates a switch button, just like IOS, in Flutter). I found it here: pub.dev, and I'm modifying it in order to add null safety. However, I have encountered a bug that I do not understand.
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:math';
/// Customable and attractive Switch button.
/// Currently, you can't change the widget
/// width and height properties.
///
/// As well as the classical Switch Widget
/// from flutter material, the following
/// arguments are required:
///
/// * [value] determines whether this switch is on or off.
/// * [onChanged] is called when the user toggles the switch on or off.
class LiteRollingSwitch extends StatefulWidget {
/// A required boolean that sets the value of the button.
/// * "on" = `true`
/// * "off" = `false`
final bool value;
/// A function called every time there is a change in state. (required)
final Function(bool) onChanged;
/// Text displayed when the [value] is `false`. By default
/// * "Off"
final String textOff;
/// Text displayed when the [value] is `true`. By default:
/// * "On"
final String textOn;
/// Color shown when the [value] is `true`. By default:
/// * `Colors.green`
final Color colorOn;
/// Color shown when the [value] is `false`. By default:
/// * `Colors.red`
final Color colorOff;
/// The size of the text. By default:
/// * `14.0`
final double textSize;
/// The duration of the animation. By default:
/// * `Duration(milliseconds: 600)`
final Duration animationDuration;
/// Text displayed when the [value] is `true`. By default:
/// * `Icons.check`
final IconData iconOn;
/// Text displayed when the [value] is `false`. By default:
/// * `Icons.flag`
final IconData iconOff;
/// The width of the switch. By default:
/// * `130`
final double width;
/// Additional action on tap.
final Function? onTap;
/// Additional action on double tap.
final Function? onDoubleTap;
/// Additional action on swipe.
final Function? onSwipe;
const LiteRollingSwitch({
Key? key,
required this.value,
required this.onChanged,
this.textOff = "Off",
this.textOn = "On",
this.textSize = 14.0,
this.colorOn = Colors.green,
this.colorOff = Colors.red,
this.iconOff = Icons.flag,
this.iconOn = Icons.check,
this.animationDuration = const Duration(milliseconds: 600),
this.width = 130,
this.onTap,
this.onDoubleTap,
this.onSwipe,
}) : super(key: key);
#override
_RollingSwitchState createState() => _RollingSwitchState();
}
class _RollingSwitchState extends State<LiteRollingSwitch>
with SingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation<double> animation;
late bool turnState;
double value = 0.0;
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
lowerBound: 0.0,
upperBound: 1.0,
duration: widget.animationDuration,
);
animation = CurvedAnimation(
parent: animationController,
curve: Curves.easeInOut,
);
animationController.addListener(() {
setState(() {
value = animation.value;
});
});
turnState = widget.value;
_determine();
}
#override
Widget build(BuildContext context) {
Color transitionColor = Color.lerp(widget.colorOff, widget.colorOn, value)!;
return GestureDetector(
onDoubleTap: () {
_action();
if (widget.onDoubleTap != null) widget.onDoubleTap!();
},
onTap: () {
_action();
if (widget.onTap != null) widget.onTap!();
},
onPanEnd: (details) {
_action();
if (widget.onSwipe != null) widget.onSwipe!();
},
child: Container(
padding: const EdgeInsets.all(5),
width: widget.width,
decoration: BoxDecoration(
color: transitionColor,
borderRadius: BorderRadius.circular(50),
),
child: Stack(
children: <Widget>[
// the "off" text
Transform.translate(
offset: Offset(10 * value, 0),
child: Opacity(
// its opacity will change
opacity: (1 - value).clamp(0.0, 1.0),
child: Container(
// it's in a container
padding: const EdgeInsets.only(right: 5),
alignment: Alignment.centerRight,
height: 40,
child: Text(
widget.textOff,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: widget.textSize,
),
),
),
),
),
// the "on" text
Transform.translate(
offset: Offset(10 * (1 - value), 0),
child: Opacity(
opacity: value.clamp(0.0, 1.0),
child: Container(
padding: const EdgeInsets.only(left: 5),
alignment: Alignment.centerLeft,
height: 40,
child: Text(
widget.textOn,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: widget.textSize,
),
),
),
),
),
// The icons
Transform.translate(
offset: Offset(widget.width / 2 * value, 0),
child: Transform.rotate(
angle: lerpDouble(0, 2 * pi, value)!,
// the white thing that will move
child: Container(
height: 40,
width: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Stack(
children: <Widget>[
// The "on" icon
Center(
child: Opacity(
opacity: value.clamp(0.0, 1.0),
child: Icon(
widget.iconOn,
size: 21,
color: transitionColor,
),
),
),
// The "off" icon
Center(
child: Opacity(
opacity: (1 - value).clamp(0.0, 1.0),
child: Icon(
widget.iconOff,
size: 21,
color: transitionColor,
),
),
),
],
),
),
),
)
],
),
),
);
}
_action() {
_determine(changeState: true);
}
/// Handles the animation.
_determine({bool changeState = false}) {
setState(() {
if (changeState) turnState = !turnState;
(turnState)
? animationController.forward()
: animationController.reverse();
widget.onChanged(turnState);
});
}
}
This happens when I click on the "off" icon of the switch:
NOTE The switcher works, but you need to understand that my error doesn't always occur. In fact, it occurs just sometimes, when I try to turn off the switcher (and only when I click very precisely on the icon)
Please help me
Try move the _action function to only be called when onTap, onDoubleTap or onPanEnd isn't null.
onDoubleTap: () {
if (widget.onDoubleTap != null) {
widget.onDoubleTap!();
_action();
}
},
onTap: () {
if (widget.onTap != null) {
_action();
widget.onTap!();
}
},
onPanEnd: (details) {
if (widget.onSwipe != null) {
widget.onSwipe!();
_action();
}
},
Full code example:
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:math';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#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: LiteRollingSwitch(
onChanged: (value) {},
onTap: () {},
value: false,
),
),
);
}
}
/// Customable and attractive Switch button.
/// Currently, you can't change the widget
/// width and height properties.
///
/// As well as the classical Switch Widget
/// from flutter material, the following
/// arguments are required:
///
/// * [value] determines whether this switch is on or off.
/// * [onChanged] is called when the user toggles the switch on or off.
class LiteRollingSwitch extends StatefulWidget {
/// A required boolean that sets the value of the button.
/// * "on" = `true`
/// * "off" = `false`
final bool value;
/// A function called every time there is a change in state. (required)
final Function(bool) onChanged;
/// Text displayed when the [value] is `false`. By default
/// * "Off"
final String textOff;
/// Text displayed when the [value] is `true`. By default:
/// * "On"
final String textOn;
/// Color shown when the [value] is `true`. By default:
/// * `Colors.green`
final Color colorOn;
/// Color shown when the [value] is `false`. By default:
/// * `Colors.red`
final Color colorOff;
/// The size of the text. By default:
/// * `14.0`
final double textSize;
/// The duration of the animation. By default:
/// * `Duration(milliseconds: 600)`
final Duration animationDuration;
/// Text displayed when the [value] is `true`. By default:
/// * `Icons.check`
final IconData iconOn;
/// Text displayed when the [value] is `false`. By default:
/// * `Icons.flag`
final IconData iconOff;
/// The width of the switch. By default:
/// * `130`
final double width;
/// Additional action on tap.
final Function? onTap;
/// Additional action on double tap.
final Function? onDoubleTap;
/// Additional action on swipe.
final Function? onSwipe;
const LiteRollingSwitch({
Key? key,
required this.value,
required this.onChanged,
this.textOff = "Off",
this.textOn = "On",
this.textSize = 14.0,
this.colorOn = Colors.green,
this.colorOff = Colors.red,
this.iconOff = Icons.flag,
this.iconOn = Icons.check,
this.animationDuration = const Duration(milliseconds: 600),
this.width = 140,
this.onTap,
this.onDoubleTap,
this.onSwipe,
}) : super(key: key);
#override
_RollingSwitchState createState() => _RollingSwitchState();
}
class _RollingSwitchState extends State<LiteRollingSwitch>
with SingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation<double> animation;
late bool turnState;
double value = 0.0;
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
lowerBound: 0.0,
upperBound: 1.0,
duration: widget.animationDuration,
);
animation = CurvedAnimation(
parent: animationController,
curve: Curves.easeInOut,
);
animationController.addListener(() {
setState(() {
value = animation.value;
});
});
turnState = widget.value;
_determine();
}
#override
Widget build(BuildContext context) {
Color transitionColor = Color.lerp(widget.colorOff, widget.colorOn, value)!;
return GestureDetector(
onDoubleTap: () {
if (widget.onDoubleTap != null) {
widget.onDoubleTap!();
_action();
}
},
onTap: () {
if (widget.onTap != null) {
_action();
widget.onTap!();
}
},
onPanEnd: (details) {
if (widget.onSwipe != null) {
widget.onSwipe!();
_action();
}
},
child: Container(
padding: const EdgeInsets.all(5),
width: widget.width,
decoration: BoxDecoration(
color: transitionColor,
borderRadius: BorderRadius.circular(50),
),
child: Stack(
children: <Widget>[
// the "off" text
Transform.translate(
offset: Offset(10 * value, 0),
child: Opacity(
// its opacity will change
opacity: (1 - value).clamp(0.0, 1.0),
child: Container(
// it's in a container
padding: const EdgeInsets.only(right: 5),
alignment: Alignment.centerRight,
height: 40,
child: Text(
widget.textOff,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: widget.textSize,
),
),
),
),
),
// the "on" text
Transform.translate(
offset: Offset(10 * (1 - value), 0),
child: Opacity(
opacity: value.clamp(0.0, 1.0),
child: Container(
padding: const EdgeInsets.only(left: 5),
alignment: Alignment.centerLeft,
height: 40,
child: Text(
widget.textOn,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: widget.textSize,
),
),
),
),
),
// The icons
Transform.translate(
offset: Offset(widget.width / 1.6 * value, 0),
child: Transform.rotate(
angle: lerpDouble(0, 2 * pi, value)!,
// the white thing that will move
child: Container(
height: 40,
width: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Stack(
children: <Widget>[
// The "on" icon
Center(
child: Opacity(
opacity: value.clamp(0.0, 1.0),
child: Icon(
widget.iconOn,
size: 21,
color: transitionColor,
),
),
),
// The "off" icon
Center(
child: Opacity(
opacity: (1 - value).clamp(0.0, 1.0),
child: Icon(
widget.iconOff,
size: 21,
color: transitionColor,
),
),
),
],
),
),
),
)
],
),
),
);
}
_action() {
_determine(changeState: true);
}
/// Handles the animation.
_determine({bool changeState = false}) {
debugPrint(changeState.toString());
setState(() {
if (changeState) turnState = !turnState;
(turnState)
? animationController.forward()
: animationController.reverse();
widget.onChanged(turnState);
});
}
}

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

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(); }

Having trouble with my horizontal scroll - flutter

I hope you are doing well today. I'm having an issue with this horizontal scroll in flutter. The images are supposed to scroll left and right and depending on the picture, you will press the button and have the ability to guess the type of pic. For some reason, images and tags don't match with images. The image names are linked to the vehicleNames list in _MyHomePageState. I have also included image_card.dart to show how ImageCard works. Thank you for the second set of eyes.
main.dart
import 'dart:ui';
import 'package:flutter/material.dart';
import 'image_card.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Guess the car!'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin{
String curVehName = "";
double scrollPercent = 0.0;
Offset startDrag;
double startDragPercentScroll;
double finishScrollStart;
double finishScrollEnd;
AnimationController finishScrollController;
List<String> vehicleNames = [
'bmw',
'ford',
'rover',
'toyota'
];
#override
initState(){
super.initState();
finishScrollController = AnimationController(
duration: Duration(milliseconds: 150),
vsync: this,
)..addListener(() {
setState(() {
scrollPercent = lerpDouble(finishScrollStart, finishScrollEnd,
finishScrollController.value);
});
});
#override
dispose(){
finishScrollController.dispose();
super.dispose();
}
}
List<Widget> buildCards(){
List<Widget> cardList = [];
for(int i = 0; i < vehicleNames.length;i++){
cardList.add(buildCard(i,scrollPercent));
print("index: ${i}");
}
return cardList;
}
Widget buildCard(int cardIndex, double scrollPercent){
final cardScrollPercent = scrollPercent / ( 1 / vehicleNames.length);
return FractionalTranslation(
translation: Offset(cardIndex-cardScrollPercent,0.0),
child: Padding(
padding: EdgeInsets.all(8.0),
child: ImageCard(imageName: vehicleNames[cardIndex],
),
),
);
}
onHorizontalDragStart(DragStartDetails details){
startDrag = details.globalPosition;
startDragPercentScroll = scrollPercent;
}
onHorizontalDragUpdate(DragUpdateDetails details){
final currentDrag = details.globalPosition;
final dragDistance = currentDrag.dx - startDrag.dx;
final singleCardDragPercent = dragDistance / context.size.width;
setState(() {
scrollPercent = ( startDragPercentScroll + ( -singleCardDragPercent
/ vehicleNames.length)).clamp(0.0, 1.0-(1/vehicleNames.length));
});
}
onHorizontalDragEnd(DragEndDetails details){
finishScrollStart = scrollPercent;
finishScrollEnd = (scrollPercent * vehicleNames.length).round()
/vehicleNames.length;
finishScrollController.forward(from: 0.0);
setState(() {
startDrag = null;
startDragPercentScroll = null;
curVehName = '';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
GestureDetector(
onHorizontalDragStart: onHorizontalDragStart,
onHorizontalDragUpdate: onHorizontalDragUpdate,
onHorizontalDragEnd: onHorizontalDragEnd,
behavior: HitTestBehavior.translucent ,
child: Stack(
children: buildCards(),
),
),
OutlineButton(
padding: EdgeInsets.all(10.0),
onPressed: (){
setState((){
this.curVehName = vehicleNames[(scrollPercent*10).round()];
});
},
child: Text(
'Show Answer',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
borderSide: BorderSide(
color: Colors.black,
width: 4.0,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
highlightedBorderColor: Colors.black,
),
Text(
curVehName,
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 2,
),
),
],
),
),
);
}
}
image_card.dart
import 'package:flutter/material.dart';
class ImageCard extends StatelessWidget{
final String imageName;
ImageCard({this.imageName});
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
border: Border.all(
color: Colors.black,
width: 4.0,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.asset(
'assets/images/$imageName.jpg',
height: 300,
fit: BoxFit.fitHeight,
),
),
);
}
}
I believe I found the issue. It seems that the
this.curVehName = vehicleNames[(scrollPercent*10).round()];
hard-coded the value of numbers needed in my vehicle names list. Once I added 10 pictures and added names to the list, it then worked as directed. The goal now is to see if I can make this a dynamic list.