How to create this curves in flutter? - 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

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

How to make text color transparent on top of container

I need to make widget this widget. For this, I need to make text transparent on top of coloured container.
You can use ShaderMask with blendMode: BlendMode.srcOut, And use it as a Child on Stack widget.
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: ShaderMask(
blendMode: BlendMode.srcOut,
shaderCallback: (bounds) {
return LinearGradient(colors: [
Colors.white,
Colors.white,
]).createShader(bounds);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"M",
style: TextStyle(
fontSize: 44,
),
),
),
),
)
You can use ShaderMask and Stack to do this
Stack(
alignment: Alignment.center,
children: [
Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://res.cloudinary.com/demo/image/upload/v1312461204/sample.jpg'),
fit: BoxFit.cover
)
),
),
ShaderMask(
blendMode: BlendMode.srcOut,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 20, 10, 20),
child: Text('M', style: TextStyle(fontSize: 30),),
),
shaderCallback: (bounds) =>
LinearGradient(colors: [Colors.white], stops: [0.0])
.createShader(bounds),
),
],
),
Here is the method that perfectly worked for me:
class SoterRiskWidget extends StatelessWidget {
final String letter;
final double fontSize;
final Color color;
final double radius;
const SoterRiskWidget({
required this.letter,
required this.fontSize,
this.radius = 2,
this.color = Colors.white,
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: fontSize,
child: CustomPaint(
painter: _CutOutTextPainter(
text: letter,
fontSize: fontSize,
fontWeight: FontWeight.w500,
color: color,
radius: radius,
),
),
);
}
}
class _CutOutTextPainter extends CustomPainter {
final String text;
final double fontSize;
final FontWeight fontWeight;
final Color color;
final double radius;
late final TextPainter _textPainter;
_CutOutTextPainter({
required this.text,
required this.fontSize,
required this.fontWeight,
required this.color,
required this.radius,
}) {
_textPainter = TextPainter(
text: TextSpan(
text: text,
style: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
),
),
textDirection: TextDirection.ltr,
);
_textPainter.layout();
}
#override
void paint(Canvas canvas, Size size) {
Offset textOffset = size.center(Offset.zero) - _textPainter.size.center(Offset.zero);
final textRect = textOffset & _textPainter.size;
Rect newRect;
double dif = ((textRect.width - textRect.height).abs()) / 2.0;
if (textRect.width > textRect.height) {
newRect = Rect.fromLTRB(
textRect.left,
textRect.top - dif,
textRect.right,
textRect.bottom + dif,
);
} else {
newRect = Rect.fromLTRB(
textRect.left - dif,
textRect.top,
textRect.right + dif,
textRect.bottom,
);
}
final boxRect = RRect.fromRectAndRadius(newRect.inflate(0), Radius.circular(radius));
final boxPaint = Paint()
..color = color
..blendMode = BlendMode.srcOut;
canvas.saveLayer(boxRect.outerRect, Paint());
_textPainter.paint(canvas, textOffset);
canvas.drawRRect(boxRect, boxPaint);
canvas.restore();
}
#override
bool shouldRepaint(_CutOutTextPainter oldDelegate) {
return text != oldDelegate.text;
}
}

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

Flutter layout issue: stack z-order not working

The z-order of stack children layout is not as my expectation.
I have 4 widgets in a stack with keys: 0,1,2,3. I try to change their z-order and rebuild the stack widget. Here is my code:
class TestBringToFrontPage extends StatefulWidget {
TestBringToFrontPage({Key key}) : super(key: key);
#override
State<TestBringToFrontPage> createState() => TestBringToFrontState();
}
class TestBringToFrontState extends State<TestBringToFrontPage> {
List<Widget> _widgets = [];
#override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
..._widgets,
Positioned(
bottom: 0,
right: 0,
child: RaisedButton(
child: Text(
"click",
style: TextStyle(color: Colors.white),
),
onPressed: () {
setState(() {
_swap(0, 2);
_swap(1, 3);
print("$_widgets");
});
},
)),
]);
}
#override
void initState() {
super.initState();
const double start = 100;
const double size = 100;
_widgets = <Widget>[
_buildWidget(key: const Key("0"), color: Colors.blue, offset: const Offset(start, start), size: const Size(size, size)),
_buildWidget(key: const Key("1"), color: Colors.green, offset: const Offset(start + size / 2, start), size: const Size(size, size)),
_buildWidget(key: const Key("2"), color: Colors.yellow, offset: const Offset(start, start + size / 2), size: const Size(size, size)),
_buildWidget(key: const Key("3"), color: Colors.red, offset: const Offset(start + size / 2, start + size / 2), size: const Size(size, size)),
];
}
Widget _buildWidget({Key key, Color color, Offset offset, Size size}) {
final label = (key as ValueKey<String>)?.value;
return Positioned(
key: key,
left: 0,
top: 0,
child: Container(
transform: Matrix4.identity()..translate(offset.dx, offset.dy, 0),
width: size.width,
height: size.height,
decoration: BoxDecoration(border: Border.all(color: Colors.black, width: 1.0), color: color),
child: Text(
label,
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w700,
decoration: TextDecoration.none,
fontSize: 15.0,
),
),
));
}
void _swap(int x, int y) {
final w = _widgets[x];
_widgets[x] = _widgets[y];
_widgets[y] = w;
}
}
At starting, stack children are laid out in z-order (from bottom to top): 0,1,2,3. Screen shows:
Starting screen state image
By clicking to the button, I expect the stack children are laid out in new z-order: 2,3,0,1. And screen should show:
Expected screen state image
But not as expected, screen shows:
Actual screen state image.
Console log when clicking the button: "[Positioned-[<'2'>](left: 0.0, top: 0.0), Positioned-[<'3'>](left: 0.0, top: 0.0), Positioned-[<'0'>](left: 0.0, top: 0.0), Positioned-[<'1'>](left: 0.0, top: 0.0)]"
In Flutter inspectors window, the stack children are in correct order: 2,3,0,1. But stack renders wrong.
Do you know where am I wrong or is it flutter layout issue?
Thank you in advance,
After playing around with your code for a little while, I've managed to figure out that the Key on each block is holding some state that messes their ordering up during setState. Removing the keys gives me the behaviour you're expecting. I don't know why this would be happening, but at least now you have a fix if the keys aren't needed for whatever your goal is.
Whether they are or not, I would file a bug report if I were you, because I'm pretty sure this behaviour isn't intended.
Here's the code I used that works (I cleaned up a little bit of it so it doesn't override initState):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: TestBringToFrontPage(),
);
}
}
class TestBringToFrontPage extends StatefulWidget {
TestBringToFrontPage({Key key}) : super(key: key);
#override
State<TestBringToFrontPage> createState() => TestBringToFrontState();
}
class TestBringToFrontState extends State<TestBringToFrontPage> {
static const start = 50.0;
static const size = 250.0;
final _widgets = <Widget>[
_buildWidget(label: "0", color: Colors.blue, offset: const Offset(start, start), size: const Size(size, size)),
_buildWidget(label: "1", color: Colors.green, offset: const Offset(start + size / 2, start), size: const Size(size, size)),
_buildWidget(label: "2", color: Colors.yellow, offset: const Offset(start, start + size / 2), size: const Size(size, size)),
_buildWidget(label: "3", color: Colors.red, offset: const Offset(start + size / 2, start + size / 2), size: const Size(size, size)),
];
#override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
..._widgets,
Positioned(
bottom: 0,
right: 0,
child: RaisedButton(
child: Text(
"click",
style: TextStyle(color: Colors.white),
),
onPressed: () {
setState(() {
_swap(0, 2);
_swap(1, 3);
print("$_widgets");
});
},
)),
]);
}
static Widget _buildWidget({String label, Color color, Offset offset, Size size}) {
return Positioned(
left: 0,
top: 0,
child: Container(
transform: Matrix4.identity()..translate(offset.dx, offset.dy, 0),
width: size.width,
height: size.height,
decoration: BoxDecoration(border: Border.all(color: Colors.black, width: 1.0), color: color),
child: Text(
label,
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w700,
decoration: TextDecoration.none,
fontSize: 30.0,
),
),
));
}
void _swap(int x, int y) {
final w = _widgets[x];
_widgets[x] = _widgets[y];
_widgets[y] = w;
}
}

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,
// ),
// ),
],
),
),
);
}
}