How to move Scaffold's body along with bottomSheet on Flutter? - flutter

I'm trying to achieve a particular behavior for my Scaffold when showing a BottomSheet. I want the Scaffold's body to move along with the bottom sheet. That is, when the Bottomheet comes out, the body of the Scaffold should go up with it. Like the image at the right. I'm not sure if my approach is the correct one. Maybe there are other better options to make this behavior possible.
The code with which I'm currently working is here:
Scaffold(
backgroundColor: Colors.purple[100],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
height: 900,
child: Builder(
builder: (context) => Container(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(_focusNode);
if (bottomSheetIsOpen) {
bottomSheetIsOpen = false;
Navigator.of(context).pop();
}
},
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 50),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
width: 300,
child: TextField(
cursorWidth: 3,
cursorColor: Colors.purple,
onTap: () {
bottomSheetIsOpen = true;
showBottomSheet(
clipBehavior: Clip.hardEdge,
context: context,
builder: (context) => Container(
child: Container(
height: 200,
color: Colors.red,
),
),
);
},
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
style: TextStyle(fontSize: 24),
showCursor: true,
readOnly: _readOnly,
),
),
Container(
height: 300,
width: 300,
color: Colors.yellow,
),
Container(
height: 250,
width: 300,
color: Colors.orange,
),
],
),
),
),
),
),
),
),
);

You could achieve this with a Stack and two AnimatedPositioned widget:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bottomsheet Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final _isOpenBottomSheet = useState(false);
return Scaffold(
appBar: AppBar(title: Text('Bottomsheet Demo')),
body: LayoutWithBottomSheet(
children: List.generate(
10,
(index) => Container(
height: 100,
color: Colors.red.withGreen(index * 25),
child: Center(
child: Text(
index.toString(),
style: TextStyle(fontSize: 24.0),
),
),
),
).toList(),
bottomSheetChild: Container(color: Colors.yellow),
bottomSheetHeight: 400,
animationSpeed: Duration(milliseconds: 300),
animationCurve: Curves.easeInOutQuad,
isOpenBottomSheet: _isOpenBottomSheet.value,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_isOpenBottomSheet.value = !_isOpenBottomSheet.value;
},
child: Icon(_isOpenBottomSheet.value
? Icons.arrow_downward
: Icons.arrow_upward),
),
);
}
}
class LayoutWithBottomSheet extends HookWidget {
final List<Widget> children;
final Widget bottomSheetChild;
final Duration animationSpeed;
final Curve animationCurve;
final double bottomSheetHeight;
final bool isOpenBottomSheet;
const LayoutWithBottomSheet({
Key key,
this.children,
this.bottomSheetChild,
this.animationSpeed,
this.animationCurve,
this.bottomSheetHeight,
this.isOpenBottomSheet,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final _scrollController = useScrollController();
final childrenBottom = useState<double>();
final bottomSheetBottom = useState<double>();
useEffect(() {
if (isOpenBottomSheet) {
childrenBottom.value = bottomSheetHeight;
bottomSheetBottom.value = 0;
if (_scrollController.hasClients) {
Future.microtask(
() => _scrollController.animateTo(
_scrollController.offset + bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
),
);
}
} else {
childrenBottom.value = 0;
bottomSheetBottom.value = -bottomSheetHeight;
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.offset - bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
);
}
}
return;
}, [isOpenBottomSheet]);
return Stack(
children: [
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
top: 0,
bottom: childrenBottom.value,
child: ListView(
controller: _scrollController,
children: children,
),
),
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
bottom: bottomSheetBottom.value,
height: bottomSheetHeight,
child: bottomSheetChild,
),
],
);
}
}

Instead of showing a bottom sheet, you can add a new widget to a Column
reserve:true is the key parameter for navigating to bottom
like:
return Scaffold(
body: SingleChildScrollView(
reserve: true,
child: Column(
children: [
YourWidget(),
if (isOpenBottomSheet)
YourBottomSheet()
],
),
),
);
the complete example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isOpenBottomSheet = false;
final _controller = ScrollController();
void _incrementCounter() {
setState(() {
isOpenBottomSheet = !isOpenBottomSheet;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
controller: _controller,
reverse: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// your widget
Container(
height: MediaQuery.of(context).size.height,
color: Colors.black),
// your bottom sheet
if (isOpenBottomSheet) Container(height: 400, color: Colors.yellow),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

you can use sliding_up_panel with parallax effect:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: SlidingUpPanel(
parallaxEnabled: true,
parallaxOffset: 0.4
panel: Center(
child: Text("This is the sliding Widget"),
),
body: Center(
child: Text("This is the Widget behind the sliding panel"),
),
),
);
}

Related

Open showModalBottomSheet from left side flutter

I am using ShowModalBottomSheet . I want to open it from left side not from bottom in flutter..
To achieve you desire UI requirements , you should create a custom modal sheet.
It is very simple , you just have to make a container and use tween animation to make it visible and it works like a modal sheet,You can change the direction of the container from its start animating and many more.
I will try to add a code for it later.
This package help you to achieve side modal sheet.
https://pub.dev/packages/side_sheet
Or there is one more way, you should visit this link,I hope it will fullfill your requirements.
https://pub.dev/packages/modal_side_sheet
I think the first package can be more helpfull for you because it has some customization options like you can choose the side etc.
in case someone still needs help. You can use a combination of overlay and Tween to create a custom side sheet.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Custom Sidesheet',
theme: ThemeData(useMaterial3: true),
home: const MyHomePage(title: 'Custom Sidesheet'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
OverlayEntry? sideSheetOverlayEntry;
final sideSheetOverlayLayerLink = LayerLink();
bool isSidebarShown = false;
#override
void initState() {
super.initState();
}
void showSideSheet() {
final sideSheetOverlay = Overlay.of(context)!;
sideSheetOverlayEntry = OverlayEntry(
builder: (context) => Positioned(
height: MediaQuery.of(context).size.height,
child: isSidebarShown
? CompositedTransformFollower(
link: sideSheetOverlayLayerLink,
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 150, end: 300),
duration: const Duration(milliseconds: 300),
builder: (BuildContext context, double size, Widget? child) {
return Material(
child: SafeArea(
child: Container(
width: size,
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
setState(() {
isSidebarShown = false;
showSideSheet();
});
},
onHover: (value) {},
child: const Icon(
Icons.close,
color: Colors.redAccent,
size: 25.0,
),
)
],
),
Column(
children: [
Container(
child: Text(
"Custom Side Sheet",
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.blueGrey),
),
),
const Divider(
height: 5.0,
color: Colors.black,
),
Container(
child: Row(children: [
Icon(
Icons.dashboard,
size: 25.0,
color: Colors.blueGrey,
),
Text(
"Home",
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.blueGrey, fontSize: 25.0, fontWeight: FontWeight.w300),
)
]),
)
],
)
],
)),
),
);
},
))
: SizedBox.shrink(),
),
);
sideSheetOverlay.insert(sideSheetOverlayEntry!);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
leading: GestureDetector(
onTap: () {
setState(() {
isSidebarShown = true;
showSideSheet();
});
},
child: Icon(
Icons.menu_sharp,
size: 30.0,
),
),
),
body: Container(
color: Color.fromRGBO(223, 230, 233, 1.0),
)),
);
}
}
side sheet not active image
side sheet active image

How to zoom an item of a list on mouse-over, keeping it always visible (Flutter on Web platform)

The problem is described like this.
In a web environment, I have to build a horizontal list of images (like in Netflix) which should increase the size of the element when the user positions the mouse cursor over them. To achieve this, I'm using a Stack (with clipBehavior equals to Clip.none) to render each item in the list, when I detect the mouse-over event I add a new Container (larger than the size of the original item) to draw an AnimatedContainer inside which will grow to fill it.
The animation works great, but the container gets positioned down to the next right item on the list, however, I need it above the item.
Here is the code:
import 'package:flutter/material.dart';
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final double zoomTargetHeight = 320;
final double zoomTargetWidth = 500;
final double zoomOriginalHeight = 225;
final double zoomOriginalWidth = 400;
double _zoomHeight = 225;
double _zoomWidth = 400;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
children: [
Image.network("https://source.unsplash.com/random/1600x900?cars"),
Container(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
const Text(
"List of items",
style: TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
SizedBox(
height: 235,
child: ListView.separated(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
),
const SizedBox(
height: 200,
),
],
),
),
),
],
),
),
);
}
Map _showZoom = {};
Widget buildCard(int index) {
Stack stack = Stack(
clipBehavior: Clip.none,
children: [
MouseRegion(
onEnter: (event) {
setState(() {
_showZoom["$index"] = true;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
),
if (_showZoom["$index"] != null && _showZoom["$index"]!)
Positioned(
left: (zoomOriginalWidth - zoomTargetWidth) / 2,
top: (zoomOriginalHeight - zoomTargetHeight) / 2,
child: MouseRegion(
onHover: (_) {
setState(() {
_zoomHeight = zoomTargetHeight;
_zoomWidth = zoomTargetWidth;
});
},
onExit: (event) {
setState(() {
_showZoom["$index"] = false;
_zoomHeight = zoomOriginalHeight;
_zoomWidth = zoomOriginalWidth;
});
},
child: SizedBox(
width: zoomTargetWidth,
height: zoomTargetHeight,
child: Center(
child: AnimatedContainer(
duration: const Duration(milliseconds: 400),
width: _zoomWidth,
height: _zoomHeight,
// color: Colors.green.withAlpha(100),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: NetworkImage(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
fit: BoxFit.cover,
),
),
),
),
),
),
),
],
);
return stack;
}
}
Remember flutter config --enable-web
I think this is precisely what you are looking for (Check also the live demo on DartPad):
The solution is:
Use an outer Stack that wraps the ListView;
Add another ListView in front of it in the Stack with the same number of items and same item sizes;
Then, ignore the pointer-events with IgnorePointer on this new ListView so the back one will receive the scroll/tap/click events;
Synchronize the scroll between the back ListView and the front one by listening to scroll events with NotificationListener<ScrollNotification>;
Here's the code
import 'package:flutter/material.dart';
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final double zoomTargetHeight = 320;
final double zoomTargetWidth = 500;
final double zoomOriginalHeight = 225;
final double zoomOriginalWidth = 400;
late final ScrollController _controllerBack;
late final ScrollController _controllerFront;
#override
void initState() {
super.initState();
_controllerBack = ScrollController();
_controllerFront = ScrollController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Image.network("https://source.unsplash.com/random/1600x900?cars"),
Container(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
const Text(
"List of items",
style: TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
SizedBox(
height: 225,
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
_controllerFront.jumpTo(_controllerBack.offset);
return true;
},
child: Stack(
clipBehavior: Clip.none,
children: [
ListView.separated(
controller: _controllerBack,
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildBackCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
IgnorePointer(
child: ListView.separated(
controller: _controllerFront,
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildFrontCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
),
],
),
),
),
const SizedBox(
height: 200,
),
],
),
),
),
],
),
),
);
}
final Map _showZoom = {};
Widget buildBackCard(int index) {
return MouseRegion(
onEnter: (event) {
setState(() {
_showZoom["$index"] = true;
});
},
onExit: (event) {
setState(() {
_showZoom["$index"] = false;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars",
),
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
);
}
Widget buildFrontCard(int index) {
Widget child;
double scale;
if (_showZoom["$index"] == null || !_showZoom["$index"]!) {
scale = 1;
child = SizedBox(
height: zoomOriginalHeight,
width: zoomOriginalWidth,
);
} else {
scale = zoomTargetWidth / zoomOriginalWidth;
child = Stack(
clipBehavior: Clip.none,
children: [
Container(
height: zoomOriginalHeight,
width: zoomOriginalWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: NetworkImage(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
fit: BoxFit.cover,
),
),
),
],
);
}
return AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: scale,
child: child,
);
}
}
I'd do something different. Instead of Stacking the zoomed-out and zoomed-in images it could be just one image with a AnimatedScale to do the transitions.
Check the code below:
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: _showZoom["$index"] == true
? zoomTargetWidth / zoomOriginalWidth
: 1,
child: Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
),
if (_showZoom["$index"] == null || _showZoom["$index"] == false)
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
Check out the screenshot and the live demo on DartPad:
All source
import 'package:flutter/material.dart';
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final double zoomTargetHeight = 320;
final double zoomTargetWidth = 500;
final double zoomOriginalHeight = 225;
final double zoomOriginalWidth = 400;
double _zoomHeight = 225;
double _zoomWidth = 400;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Image.network("https://source.unsplash.com/random/1600x900?cars"),
Container(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
const Text(
"List of items",
style: TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
SizedBox(
height: 235,
child: ListView.separated(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
),
const SizedBox(
height: 200,
),
],
),
),
),
],
),
),
);
}
Map _showZoom = {};
Widget buildCard(int index) {
Stack stack = Stack(
clipBehavior: Clip.none,
children: [
MouseRegion(
onEnter: (event) {
setState(() {
_showZoom["$index"] = true;
});
},
onExit: (event) {
setState(() {
_showZoom["$index"] = false;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: _showZoom["$index"] == true
? zoomTargetWidth / zoomOriginalWidth
: 1,
child: Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
),
if (_showZoom["$index"] == null || _showZoom["$index"] == false)
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
),
],
);
return stack;
}
}

I am trying to change the color of a container onTap but for some reason it isnt working

This is the code that I used. I am trying to implement a UI where a quiztaker can see highlighted the option that they selected.
I am new to this please tell me where I went wrong?
#override
Widget build(BuildContext context) {
int selectedOption = 0;
return Scaffold(
key: scaffoldKey,
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Card(
clipBehavior: Clip.antiAliasWithSaveLayer,
color: Color(0xFFF5F5F5),
// onTap is here
child: InkWell(
onTap: () { setState(() {
selectedOption = 1;
}
);
},
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: selectedOption == 1 ? Colors.black: Colors.cyan,
),
child: Text(
'Hello World',
style: TextStyle(),
),
),
),
),
],
),
),
);
}
}
You need to move 'selectedOption' variable to outside of 'build' method
because when call 'setState', build is called and variable is reset.
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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedOption = 0;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Card(
clipBehavior: Clip.antiAliasWithSaveLayer,
color: Color(0xFFF5F5F5),
// onTap is here
child: InkWell(
onTap: () {
setState(() {
selectedOption = 1;
});
},
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: selectedOption == 1 ? Colors.black : Colors.cyan,
),
child: Text(
'Hello World',
style: TextStyle(),
),
),
),
),
],
),
),
);
}
Widget _buildBody() {
return Container();
}
}
With the current code Container color is updating only once. You need to change your code as follows to get the required output
return Scaffold(
key: scaffoldKey,
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Card(
clipBehavior: Clip.antiAliasWithSaveLayer,
color: Color(0xFFF5F5F5),
// onTap is here
child: InkWell(
onTap: () {
setState(() {
_isSelected = !_isSelected;
});
},
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isSelected ? Colors.black : Colors.cyan,
),
child: Text(
'Hello World',
style: TextStyle(),
),
),
),
),
],
),
),
);
Instead of using the int variable you should use a boolean and update its state when tapped.
Please move selectedOption to state class. then setstate() will work.

List widgets state is not updating with StatefulBuilder in flutter

My question is setstate is not working with the statefulbuilder and list of widgets I want to update color of the container.
Although color of button is updating. I am not sure what is the problem
Color currentColor = Colors.grey;
void changecolor(Color color) {
setState(() {
currentColor = color;
});
}
List<Widget> list = [];
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
Positioned(
right: 50,
top: 50,
child: RaisedButton(
elevation: 3.0,
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
titlePadding: const EdgeInsets.all(0.0),
contentPadding: const EdgeInsets.all (0.0),
content: SingleChildScrollView(
child: MaterialPicker(
pickerColor: currentColor,
onColorChanged: changecolor,
enableLabel: true,
),
),
);
},
);
},
child: const Text('Change me'),
color: currentColor,
),
),
...list,
Positioned(
left: 50,
top: 50,
child: RaisedButton(
child: Text(
'Add another Color Sticker',
style: TextStyle(fontSize: 10),
),
onPressed: () {
setState(
() {
list.add(
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Positioned(
left: 100,
top: 100,
child: Container(
color: currentColor,
height: 100,
width: 100,
),
);
},
),
);
},
);
},
),
),
],
),
);
Can anyone help me with this issue I am not able to understand why it is not updating the color of the container
Thanks in advance.
You can copy paste run full code below
In this case, you can use ValueNotifier<Color> and ValueListenableBuilder
code snippet
ValueNotifier<Color> currentColor = ValueNotifier(Colors.grey);
...
MaterialPicker(
pickerColor: currentColor.value,
...
child: const Text('Change me'),
color: currentColor.value,
...
list.add(ValueListenableBuilder(
valueListenable: currentColor,
builder: (BuildContext context, Color current,
Widget child) {
return Positioned(
left: 100,
top: 100,
child: Container(
color: current,
height: 100,
width: 100,
),
);
}));
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.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: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, 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: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
ValueNotifier<Color> currentColor = ValueNotifier(Colors.grey);
void changecolor(Color color) {
setState(() {
currentColor.value = color;
});
}
List<Widget> list = [];
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
Positioned(
right: 50,
top: 50,
child: RaisedButton(
elevation: 3.0,
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
titlePadding: const EdgeInsets.all(0.0),
contentPadding: const EdgeInsets.all(0.0),
content: SingleChildScrollView(
child: MaterialPicker(
pickerColor: currentColor.value,
onColorChanged: changecolor,
enableLabel: true,
),
),
);
},
);
},
child: const Text('Change me'),
color: currentColor.value,
),
),
...list,
Positioned(
left: 50,
top: 50,
child: RaisedButton(
child: Text(
'Add another Color Sticker',
style: TextStyle(fontSize: 10),
),
onPressed: () {
setState(
() {
list.add(ValueListenableBuilder(
valueListenable: currentColor,
builder: (BuildContext context, Color current,
Widget child) {
return Positioned(
left: 100,
top: 100,
child: Container(
color: current,
height: 100,
width: 100,
),
);
}));
},
);
},
),
),
],
),
);
}
}

How to animate Alert dialog position in Flutter?

By this simple code I can show dialog on bottom of screen like with this screenshot:
But I have three simple issue:
set margin on bottom of dialog such as 20.0 on showing dialog
using controller.reverse() on dismiss dialog
dismiss dialog on click on outside of dialog
Full source code:
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 300, end: 280);
Animation<double> marginTopAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
marginTopAnimation = marginTopTween.animate(controller)
..addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(controller),
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
left:20.0,
right:20.0,
),
color: Colors.red,
child: Text("Container"),
),
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
Screenshot:
Code:
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(bottom: 50, left: 12, right: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
)
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: CityPage()));
}
class CityPage extends StatelessWidget {
const CityPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
child: const Text('Press me'),
onPressed: () => BottomDialog().showBottomDialog(context),
),
),
],
),
);
}
}
class BottomDialog {
void showBottomDialog(BuildContext context) {
showGeneralDialog(
barrierLabel: "showGeneralDialog",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.6),
transitionDuration: const Duration(milliseconds: 400),
context: context,
pageBuilder: (context, _, __) {
return Align(
alignment: Alignment.bottomCenter,
child: _buildDialogContent(),
);
},
transitionBuilder: (_, animation1, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(animation1),
child: child,
);
},
);
}
Widget _buildDialogContent() {
return IntrinsicHeight(
child: Container(
width: double.maxFinite,
clipBehavior: Clip.antiAlias,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Material(
child: Column(
children: [
const SizedBox(height: 16),
_buildImage(),
const SizedBox(height: 8),
_buildContinueText(),
const SizedBox(height: 16),
_buildEmapleText(),
const SizedBox(height: 16),
_buildTextField(),
const SizedBox(height: 16),
_buildContinueButton(),
],
),
),
),
);
}
Widget _buildImage() {
const image =
'https://user-images.githubusercontent.com/47568606/134579553-da578a80-b842-4ab9-ab0b-41f945fbc2a7.png';
return SizedBox(
height: 88,
child: Image.network(image, fit: BoxFit.cover),
);
}
Widget _buildContinueText() {
return const Text(
'Continue with account',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildEmapleText() {
return const Text(
'example.com',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildTextField() {
const iconSize = 40.0;
return Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey.withOpacity(0.4)),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Row(
children: [
Container(
width: iconSize,
height: iconSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
),
child: const Center(
child: Text('Е'),
),
),
const SizedBox(height: 16),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'elisa.g.beckett#gmail.com',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
Text('**********'),
],
)
],
),
);
}
Widget _buildContinueButton() {
return Container(
height: 40,
width: double.maxFinite,
decoration: const BoxDecoration(
color: Color(0xFF3375e0),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: RawMaterialButton(
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
child: const Center(
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}
From top to bottom you can use
bool _fromTop = true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(top: 50, left: 12, right: 12, bottom: 50),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
),
);
}
Output:
Not sure if I got your question clearly, if this is what you are looking for, replace your PopUp class with mine.
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',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with TickerProviderStateMixin {
AnimationController controller;
double _bottom = 0, _fromTop = 300, _screenHeight, _containerHeight = 300;
#override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this)
..addListener(() {
Timer.periodic(Duration(milliseconds: 15), (timer) {
if (_bottom < _screenHeight - _fromTop - _containerHeight) {
_bottom += 1;
setState(() {});
}
});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
_screenHeight = MediaQuery.of(context).size.height;
return SizedBox(
width: double.infinity,
height: double.infinity,
child: Stack(
children: <Widget>[
Positioned(
bottom: _bottom,
left: 0,
right: 0,
child: Container(height: _containerHeight, color: Colors.green),
),
],
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
I think showModalBottomSheet function does this out of the box.