I am trying to build a multi-page registration screen with each page having a Form displayed within a PageView for each and smooth sliding effect. My only trouble is when the keyboard is displayed, the Form doesn't scroll up and other fields get hidden behind the keyboard. Have a look at the screenshot.
The main registration page has Scaffold which has a Stack as body. PageView is a child of the Stack with a few other widgets, header back button and footer next button.
Below is the main registration page scaffold.
return Scaffold(
body: SafeArea(
child: Stack(
children: [
// Back Button
// Form Pages
Padding(
padding: const EdgeInsets.only(top: 40),
child: PageView.builder(
controller: _formsPageViewController,
itemBuilder: (BuildContext context, int index) {
return _forms[index];
},
),
),
// Form Button
],
),
),
);
Below is the build method of the PageView Form1 page.
Widget build(BuildContext context) {
return ConstrainedBox(
constraints:
BoxConstraints(maxHeight: MediaQuery.of(context).size.height / 2),
child: SingleChildScrollView(
physics: null,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Page Title
//... code removed for title text
// Image Avatar
// ... code removed for Avatar image
// Form
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Column(
children: [
// Name
// ... Code removed for textbox
// Govt. Registration No
// ... Code removed for textbox
// Establishment Year and Month
Row(
children: [
// Established Month
Flexible(
child: // ... Code removed for dropdown
),
// Established Year
Flexible(
child: // .. Code removed for dropdown
)
],
),
// ... code removed for next button
],
),
),
),
],
),
),
);
} // Build
you disabled the SingleChildScrollView's physics inside the form, change it to this:
ConstrainedBox(
constraints:
BoxConstraints(maxHeight: MediaQuery.of(context).size.height / 2),
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(), // <--- change to this
....
)
Related
I created onboarding in my flutter app:
This is my data model:
class Data {
final String image;
final String title;
final String desc;
Data(this.image, this.title, this.desc); }
final List<Data> items = [
Data('assets/1.png', '1', '1'),
Data('assets/2.png', '2', '2'),
Data('assets/3.png', '3', '3')
];
This is my code:
Scaffold(
body: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
PageView.builder(
controller: _pageController,
itemBuilder: (BuildContext context, int index) {
var item = items[index];
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(item.image),
fit: BoxFit.cover,
),
),
//bloc1
child: Padding(
padding: const EdgeInsets.only(bottom: 80),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [Text(item.title), Text(item.desc)],
),
),
);
},
),
//bloc 2
SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Padding(
padding: EdgeInsets.only(bottom: 10, top: 10),
child: Text('****dots indicators****'),
),
TextButton(
onPressed: () {},
child: Text('Next'),
)
],
))
],
),
);
This onBoading consists of three parts:
background image
text block - block1
"lower action block" - block2.
I need to have a small fixed distance between blocks 1 and 2 - 10pх.
but also I can't place block1 and block2 together. I need block1 to be "inside" PageView, block2 on the main page.
right now I'm using the bottom padding. but this is not a reliable solution - this size may change on different screens - "overlapping" will occur.
how can I fix this problem? Any advice - I'll be very grateful.
If you want to position your "page indicator dots" at a fixed relative spot, for example, 10% from the bottom of the screen, an easy solution is to use the Align widget.
However in your case, you must also change the Column in your to MainAxisSize.min, so that its height will be as small as possible, leaving you enough freedom to decide where to position the Column vertically. For example:
SafeArea(
child: Align(
alignment: Alignment(0, 0.95), // relative position
child: Column(
mainAxisSize: MainAxisSize.min, // make Column shorter
children: [
Text('****dots indicators****'),
TextButton(
onPressed: () {},
child: Text('Next'),
)
],
),
),
),
For the alignment property of the Align widget, the values are (x, y) ranging from -1 to +1, with 0 being the center. For example, you can use alignment: Alignment(0, -0.5) to make it center horizontally, and 50% towards the top on the vertical axis.
This is my code until now, I would like to anchor the Material button to the bottom of the Screen, so I would like that my Column to take all the available space on the screen, I cannot use the Scaffold arguments like bottomsheet for this, since it is another widget and there is some separate logic that requires the button to be in the same place as the listView
Scaffold(
body: Column(children[
Container(),
Container(),
_myWidget(),
]));
Widget _myWidget(){
return Expanded(
child: Column(
children:[
Container(),
ListView.builder( /* more_code*/ ),
Align(
alignment: Alignment.bottomCenter,
child: MaterialButton(/* more_code */)
),],),
);
This is how the button is now, I want to anchor it to the bottom
Write with stack instead of column
Widget _myWidget(){
return Expanded(
child: Stack(
children:[
Container(),
ListView.builder( /* more_code*/ ),
Align(
alignment: Alignment.bottomCenter,
child: MaterialButton(/* more_code */)
),],),
);
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())
),
)
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();
});
}
}
Sorry bad english.. I want to navigate from one screen to another base on id from gridview items.
when one of the listview item11 is clicked it will be directed to the detail page item11..
So, how do create a detailed page design when one of the ListView's clicks is different.
full my code:
class CategoryScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new SafeArea(
child: new Scaffold(
appBar: new BankAndaAppBar(),
body: new Container(
color: Colors.grey[100],
child: new ListView(
physics: ClampingScrollPhysics(),
children: <Widget>[
new Container(
padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 12.0),
color: Colors.grey[100],
child: new Column(
children: <Widget>[
_bodyGridView(),
],
)),
],
),
),
),
);
}
Widget _bodyGridView() {
return SingleChildScrollView(
child: new Column(
children: <Widget>[
GridView.count(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
crossAxisCount: 3,
children: CATEGORIES_DUMMY_DATA
.map((cat) => ListCategory(cat.id, cat.title, cat.titleDetail, cat.description, cat.image))
.toList(),
),
],
),
);
}
}
I want display detailed information after clicking a gridview item. example : every clicking a item 1 show detail view item=1.
enter image description hereThanks.
As per my understanding, you want to display detailed information after clicking a grid item.
If so, there are many ways to do it.
Pass the data within pages : follow
Create a model class, store the data in memory and display it. follow
If you want to navigate from one screen to another follow