In the screenshot, I want to get the next and back buttons to the bottom of the screen.
The stepper has a parameter, controlsBuilder
that allows you to build out the layout for the controls. If it's just a simple row, It's placed right underneath the content.
Apparently, the Stepper is a flexible wrapper. I'm not sure what that means. I think it means that the Stepper is considered a flex object because it contains a scrollable area (for the content). Having read the docs, if I'm understanding correctly, it says that I cannot use an Expanded
or a Column
with a max size in the mainAxis because the stepper is essentially a scrollable area, meaning any RenderBox inside it has unbounded constraints.
So, what are some ways the controls builder can be pushed down to the bottom?
Widget _createEventControlBuilder(BuildContext context, {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FlatButton(
onPressed: onStepCancel,
child: const Text('BACK'),
),
FlatButton(
onPressed: onStepContinue,
child: const Text('NEXT'),
),
]
);
}
I did try wrapping the above row in a LayoutBuilder as well as another attempt using a SizedBox, setting the height to MediaQuery.of(context).size.height;
. It does push it near to the bottom (not quite as much as I like), but the problem is that now there is space beneath controls, causing the screen to scroll down into empty space.
Full code:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Create an Event"),
),
body: Form(
key: _eventFormKey,
child: Stepper(
type: StepperType.horizontal,
currentStep: _currentStep,
controlsBuilder: _createEventControlBuilder,
onStepContinue: () {
if (_currentStep + 1 >= MAX_STEPS)
return;
setState(() {
_currentStep += 1;
});
},
onStepCancel: () {
if (_currentStep + 1 >= MAX_STEPS)
return;
setState(() {
_currentStep -= 1;
});
},
steps: <Step>[
Step(
title: Text("Name"),
isActive: 0 == _currentStep,
state: _getStepState(0),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 10.0),
child: Text(
"Give your event a cool name",
style: Theme.of(context).textTheme.title,
),
),
TextFormField(
maxLines: 1,
maxLength: 50,
maxLengthEnforced: true,
decoration: InputDecoration(
hintText: "e.g. Let's eat cheeseburgers!",
),
validator: (value) {
if (value.trim().isEmpty)
return "Event name required.";
},
)
],
)
),
Step(
title: Text("Type"),
isActive: 1 == _currentStep,
state: _getStepState(1),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 10.0),
child: Text(
"Select an event type",
style: Theme.of(context).textTheme.title,
),
),
Container(
margin: EdgeInsets.only(bottom: 10.0),
child: Row(
children: <Widget>[
Expanded(
child: DropdownButton<int>(
items: _stepTwoDropdownItems,
hint: Text("Select event type"),
isExpanded: true,
value: _eventTypeSelectedIndex,
onChanged: (selection) {
setState(() {
_eventTypeSelectedIndex = selection;
});
}),
)
],
)
)
],
)
),
]
),
),
);
}
To customize the color, border, etc., wrap a stepper widget inside a Container and specify it's decoration argument.
you can customize the whole stepper widget to delete the padding, follow these steps: create dart file called custom_stepper for example. put the code at the bottom here in this file(note that it's very big code) use the CustomStepper widget not Stepper.
Just set type: StepperType. horizontal .
Use Your Existing Theme Colors. For some reason, stepper does not inherit your main MaterialApp() 's theme. But you can wrap your Stepper() widget with Theme() and use your primary theme's colors anyways.
I think you can create your own Stepper
, or you can try this 'hack' :
Create two variables to store the callbacks:
VoidCallback _onStepContinue;
VoidCallback _onStepCancel;
Put your Form
inside a Stack
:
Stack(
children: <Widget>[
Form(
child: Stepper(
Change your createEventControlBuilder method:
Widget _createEventControlBuilder(BuildContext context,
{VoidCallback onStepContinue, VoidCallback onStepCancel}) {
_onStepContinue = onStepContinue;
_onStepCancel = onStepCancel;
return SizedBox.shrink();
}
Add your custom buttoms :
Widget _bottomBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FlatButton(
onPressed: () => _onStepCancel(),
child: const Text('BACK'),
),
FlatButton(
onPressed: () => _onStepContinue(),
child: const Text('NEXT'),
),
]);
}
This is how your Stack
will looks like :
Stack(
children: <Widget>[
Form(
child: Stepper(
....
), //Form
Align(
alignment: Alignment.bottomCenter,
child: _bottomBar(),
)
I know this is a little dirty, but you could try , otherwise I recommend you to create your own Widget.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With