How to fix a button at bottom of a single child scrollview with a list - flutter

I have a SingleChildScrollView and inside it I have a list with some cards, that you can remove ou add more. I need to fix an add button at the bottom of the screen, when the card list is not scrollable yet, but when the card list increase size and the scrollview is able to scroll now (to see all the content), the button must follow the list and not keep fixed at the bottom anymore.
For now, what I did to solve this, was check the scroll view every time that a card is added ou removed, if I checked that the screen is now scrollable or not scrollable I change some properties of my build widget:
SingleChildScrollView(
controller: _scrollController,
physics: AlwaysScrollableScrollPhysics(),
child: Container(
height: isNotScrollable
? _pageSize - (_appBarSize + _notifySize)
: null,
padding: const EdgeInsets.symmetric(
horizontal: Constraints.paddingNormal),
child: Column(
.....
and after the list render I create the button like this
isNotScrollable
? Expanded(
child: Container(),
)
: Container(),
CVButton(
color: Palette.white,
Basically, my idea is: if the screen is not scrollable yet (the list content fits in the screen size) I will set a height to the container inside scrollview and add a Expanded() widget before the add button (so the button will stay in the bottom of the container), but if the screen is scrollable (the list content not fits inside the screen size) so I remove the container height and the Expanded widget, then the button will follow the list now as normally.
I don't know if this is the better way to deal with that, I want to know if there is some way to do this without this 'dinamic' way that I am doing, only with fixed widgets and not changing the widget according to the state of the scrollview.
An example when the list becomes scrollable and the button will keep at list bottom
Here the list is not scrollable yet but the button must be at the screen bottom and not list bottom
(I dont wanna use bottomNavBar)
Anyone has any idea how I can solve this?

I have a solution for this. check the code bellow. I added some buttons to add or remove cards. The main trick is to use constraints like minHeight.
import 'package:flutter/material.dart';
class BottomButton extends StatefulWidget {
#override
_BottomButtonState createState() => _BottomButtonState();
}
class _BottomButtonState extends State<BottomButton> {
List<Widget> cards = [];
#override
Widget build(BuildContext context) {
var appBar2 = AppBar(
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_addCard();
}),
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
_removeCard();
}),
],
);
return Scaffold(
appBar: appBar2,
body: Container(
height: MediaQuery.of(context).size.height -
(MediaQuery.of(context).padding.top + appBar2.preferredSize.height),
alignment: Alignment.topCenter,
child: ListView(
primary: true,
children: [
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height -
(MediaQuery.of(context).padding.top +
appBar2.preferredSize.height),
),
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height -
(MediaQuery.of(context).padding.top +
appBar2.preferredSize.height +
50),
),
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: cards,
),
),
ElevatedButton(
child: Text('this is a button'),
onPressed: () {},
),
],
),
)
],
),
),
);
}
void _addCard() {
Widget card = Card(
child: Container(
height: 100,
color: Colors.red,
padding: EdgeInsets.all(2),
),
);
setState(() {
cards.add(card);
});
}
void _removeCard() {
setState(() {
cards.removeLast();
});
}
}

Related

Flutter - Update items in a ListView

I'm trying to change the order of widgets in a ListView as follows:
Widgets List
List<Widget> widgets = [const Text("Widget 1"), const Text("Widget 2")];
ListView
ListView(children: widgets),
Text("$widgets")
Function
() {
widgets.shuffle();
setState(() {
widgets = widgets;
});
}
This way the order of widgets is changed in the TEXT but not in the list as I intended.
Any idea?
First solution: Add Key to List ListView like this:
ListView(
key: UniqueKey(),// <--- add this
children: widgets,
),
also in your Function you don't need to pass widgets again:
() {
widgets.shuffle();
setState(() {});
}
Second solution: you can use SingleChildScrollView and Column like this:
Padding(
padding: const EdgeInsets.all(20.0),
child: SizedBox(
height: 50,
width: 100,
child: SingleChildScrollView(
child: Column(
children: widgets,
),
),
),
),

How to create a form with add more field in flutter using flutter_form_builder?

Flutter Web
So I have a button called add tags which opens up a modal. The Modal has only one text field and two buttons called add another tag and submit.
Now what I want to do is when the user clicks the add another tag button the app will generate another text field.
I've already seen some videos and read the documentation but since I need to work on a modal and the modal has defined size I'm not sure how to handle issues like
What happens if the user adds a lot of tags. How can I make the modal scrollable?
I'm new to flutter_form_builder so I'm not sure if the modal can handle it or not.
Here's my code:
final _formKey = GlobalKey<FormBuilderState>();
Future buildAddTagsForm(BuildContext context,
{Function()? notifyParent}) async {
return await showDialog(
barrierDismissible: false,
barrierColor: Colors.black.withOpacity(0.5),
context: context,
builder: (context) {
var screen = MediaQuery.of(context).size;
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
content: SingleChildScrollView(
child: Container(
height: screen.height / 2,
width: screen.height > 650 ? 600.00 : screen.height * 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilder(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(
Icons.cancel_presentation_rounded,
),
),
],
),
SizedBox(
height: 10,
),
FormBuilderTextField(
name: 'Tag Name',
decoration: InputDecoration(labelText: 'Tag name'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MaterialButton(
color: CustomColors.buttonColor,
child: Text(
"Add another tag",
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {},
)
],
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MaterialButton(
color: CustomColors.buttonColor,
child: Text(
"Submit",
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {},
)
],
),
],
),
),
),
),
),
);
},
);
},
);
}
I'm assuming by "modal" we're talking about the AlertDialog here:
return AlertDialog(
content: SingleChildScrollView(
By using SingleChildScrollView as the AlertDialog content:, we can have any size / any number of text fields we like in the dialog. If their number are too many for the height of dialog inside our screen, the content will scroll.
Although, its immediate child Container with height prevents the SingleChildScrollView from doing its magic:
return AlertDialog(
content: SingleChildScrollView(
child: Container(
height: screen.height / 2,
I think the above AlertDialog would not scroll because it would never be big enough to need to scroll. Plus, any fields added that combine to be taller than that specified height (screen.height / 2) will cause an overflow warning and be cutoff visually.
So to answer question #1: "What happens if the user adds a lot of tags. How can I make the modal scrollable?"
using SingleChildScrollView is the right idea
lets swap the position of the Container with height and the SingleChildScrollView and this should allow the dialog to grow & scroll as needed as columns in FormBuilder increase
Your question #2: "I'm new to flutter_form_builder so I'm not sure if the modal can handle it or not."
flutter_form_builder shouldn't affect how SingleChildScrollView works
Example
Here's a partial example of an AlertDialog with scroll view content: that can grow in number.
Widget build(BuildContext context) {
return Container(
height: 300,
child: AlertDialog(
content: SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: items,
),
),
),
actions: [
OutlinedButton(
child: Text('Add Row'),
onPressed: _incrementCounter
)
]
),
);
}
The complete example runnable in DartPard is here. (Add a 6 or 7 rows and then scroll the content.)
Warning
There's a gotcha with using the above AlertDialog inside a sized Container. That Container with height is not enough to constrain the AlertDialog size.
Your showDialog builder: (that pushes the AlertDialog into existence) must provide additional constraints in order for the sized Container to have constraints to size itself within. Without these constraints, the AlertDialog will grow until it matches the device viewport size. I believe this is a quirk with how showDialog is written, since I'm guessing it's a modal layer on top of the current stack of routes. (Someone can correct me if I'm wrong.) It's only constraint is the physical device, but nothing else. By wrapping builder:'s output with a constraining widget (such as Center) the output will be able to size itself.
To see this in action, remove the Center widget from the full example above an re-run it. The dialog will grow to fill the screen when adding rows instead of being at max 300px in height.
child: OutlinedButton(
child: Text('Open Dialog'),
onPressed: () => showDialog(
context: context,
builder: (context) => Center(child: MyDialog())
),
)

Trying to Understand ConstrainedBox

Flutter Newbie here.
Please refer to this example from Flutter docs Example
I have added just 4 lines to that code. You will find those lines which has comment "Lines Added by me".
But there is no change in the output.
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.bodyText2,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
print(viewportConstraints.minWidth); // Lines Added By me
print(viewportConstraints.maxWidth); // Lines Added By me
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
maxWidth : viewportConstraints.minWidth/2, // Lines Added By me
minWidth : viewportConstraints.minWidth/2, // Lines Added By me
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
// A fixed-height child.
color: const Color(0xffeeee00), // Yellow
height: 120.0,
alignment: Alignment.center,
child: const Text('Fixed Height Content'),
),
Container(
// Another fixed-height child.
color: const Color(0xff008000), // Green
height: 120.0,
alignment: Alignment.center,
child: const Text('Fixed Height Content'),
),
],
),
),
);
},
),
);
}
So I am just playing with that example. And i wanted to see that if i can control the width of containers by providing maxWidth to constrainedBox. But its not working and i want to know reason/concept behind that?
Now in section layout behaviour of container says that "If the widget has an alignment, and the parent provides bounded constraints, then the Container tries to expand to fit the parent, and then positions the child within itself as per the alignment."
Can someone explain me where i am wrong in my understanding ?
From the above ScreenShot it seems that proper(width/2) constraints are not passed to column.

Color of a widget inside a Stack is always slightly transparent

I display a custom-made bottom app bar in a Stack because of keyboard padding reasons. The custom widget is fully opaque as it should be until it's a child of a Stack in which case, the content behind it starts to be visible since the color's opacity somehow changes.
As you can see, it's only the "main" color that's transparent. Icons remain opaque.
This is the build method of my custom BottomBar widget which is then just regularly put into a Stack. I have tried using a Material and even a simple Container in place of the BottomAppBar widget but the results are the same.
#override
Widget build(BuildContext context) {
return BottomAppBar(
color: Colors.blue.withOpacity(1),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(MdiIcons.plusBoxOutline),
onPressed: () {},
),
Text('Edited 11:57'),
IconButton(
icon: Icon(MdiIcons.dotsVertical),
onPressed: () {},
),
],
),
);
}
Can you interact with the BottomAppBar ? It looks like an order problem. Try to put the BottomAppBar as last in the Stack children.
Note that BottomAppBar doesn't have a constant size, if you did not add it to Scaffold bottomNavigationBar named parameter has a size if this is not null. Below is peace of code in Scaffold dart file:
double bottomNavigationBarTop;
if (hasChild(_ScaffoldSlot.bottomNavigationBar)) {
final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height;
bottomWidgetsHeight += bottomNavigationBarHeight;
bottomNavigationBarTop = math.max(0.0, bottom - bottomWidgetsHeight);
positionChild(_ScaffoldSlot.bottomNavigationBar, Offset(0.0, bottomNavigationBarTop));
}
You can even develop your own Widget without BottomAppBar but if you want things like centerDocked and things like circular notched, you will have to do more stuff (anyway you have flexibility to custom design the way you want).
Here is a simple example to do that(one way to do that):
import 'package:flutter/material.dart';
class CustomBottomBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 50),
color: Colors.greenAccent, // if you want this color under bottom bar add the margin to list view
child: ListView.builder(
itemCount: 100,
itemBuilder: (_, int index) => Text("Text $index"),
),
),
Positioned(
bottom: 0,
child: Container(
color: Colors.amber.withOpacity(.5),
width: MediaQuery.of(context).size.width,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(4, (int index) => Text("Text $index")), // you can make these clickable by wrapping with InkWell or any gesture widget
),
),
),
],
),
);
}
}

How to Center SingleChildScrollView but make background stretch to fill screen?

I am use Flutter for web for make website. I want make webpage scroll when user scroll down like normal website.
I am try use Stack so I can place custom background behind widgets. This background must scroll when user scroll (must stick to widgets in front so background change).
(I cannot set background color using Scaffold because my background is use CustomPainter)
But I want center the widgets on webpage, so I wrap SingleChildScrollView in Center widget. But now on large horizontal screen the CustomPaintWidget() is not fill screen (there is blank space). I have try replace my CustomPaintWidget() with Container to test, but same issue.
Here my code:
Center(
child: SingleChildScrollView(
child:Stack(children: <Widget>[
CustomPaintWidget(),
Widgets(),
],),
Anyone know solution?
How to center widgets but also make background stretch?
Thanks!
SingleChildScrollView by definition shriknwraps it's child.
What you should try is
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
//Use MediaQuery.of(context).size.height for max Height
constraints: BoxConstraints(minHeight: MediaQuery.of(context).size.height),
child: Center(
child: //Widget,
),
),
);
I think you can try something like:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
CustomPaintWidget(),
Center(
child: SingleChildScrollView(
child: Widgets(),
),
)
],
));
}
read that post, I think is all you need https://medium.com/#swav.kulinski/spike-parallax-in-flutter-seven-lines-of-code-16a1890d8d32
I know it is too late to answer but s.o may need it in future
You have to use Stack
for instance:
your MainClass:
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
ScrollViewClass(),
Column(
children: [
//YOUR ITEMS
]),
);
ScrollviewClass:
class ScrollViewClass extends StatefulWidget {
#override
_ScrollViewClassState createState() => _ScrollViewClassState();
}
class _ScrollViewClassState extends State<ScrollViewClass> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.only(top: 260, bottom: 100),
child: ListView(
children: [
Container(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: ConstrainedBox(
//Use MediaQuery.of(context).size.height for max Height
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height),
child: Column(
children: [
//ADD YOUR ITEMS LIKE IMAGE, TEXT, CARD ETC...
Center(child: Image.asset('assets/app_name.png')),
Center(child: Image.asset('assets/app_name.png')),
Center(child: Image.asset('assets/app_name.png')),
Center(child: Image.asset('assets/app_name.png')),
Center(child: Text('fdgdfg')),
Center(child: Text('fdgdfg')),
],
)),
),
)
],
),
));
}
}
I know this is not the OP's scenario, but for others - If there is something above your scroll view, using the full height of the page will cause the scrollview to scroll prematurely, because the combined height of the widgets is now greater than the page height. Use LayoutBuilder instead of MediaQuery.of(context).size.height.
LayoutBuilder(builder: ((context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight),
child: Center(child: child)),
);
})