Here is what I'm trying to achieve:
On a smaller device, where all the input fields can't be
displayed on a single page, the form should be like this:
But on a bigger screen, where all the Input fields
can be displayed on one page, I want it to be like this:
(Notice the spacing between the button and the last input
field should be dynamic, so that even on tablets the button
will be on the bottom of the screen)
Please before providing an answer also check how it reacts to the keyboard being opened, I don't want it to make the button move upwards... like this:
Also, of course, I don't want the keyboard to cause overflows, or make the input fields not accessible, I have tried many things, and I haven't found a way to achieve it, even though it seems to be a simple problem.
I am using a LayoutBuilder
, SingleChildScrollView
and ConstrainedBox
to give the min height to the Column
.
To place the button at the bottom when there are enough room, use a Spacer
, and using a Spacer
inside SingleChildScrollView
would also need to warp the Column
with an IntrinsicHeight
.
This approach is demonstrated in the documentation of SingleChildScrollView.
To avoid the keyboard pushing the button, we need to get the viewInsets using MediaQuery.viewInsetsOf(context)
and adjust it to give the min height to the Column
.
class A extends StatefulWidget {
const A({super.key});
@override
State<A> createState() => _AState();
}
class _AState extends State<A> {
int _number = 5;
@override
Widget build(BuildContext context) {
final viewInsets = MediaQuery.viewInsetsOf(context);
return Scaffold(
appBar: AppBar(
title: const Text('Demo'),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
final minHeight = constraints.maxHeight + viewInsets.bottom;
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: minHeight),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('ViewInsets: $viewInsets'),
Text('Constraints: $constraints'),
Text('minHeight: $minHeight'),
],
),
),
for (var i = 0; i < _number; i++)
const Card(
child: TextField(),
),
// This is for demo only, we could simply use a spacer here
const Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(color: Colors.red)),
),
),
),
DecoratedBox(
decoration: const BoxDecoration(
border: Border.fromBorderSide(BorderSide(color: Colors.blue)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton.filled(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
_number++;
});
},
),
IconButton.filled(
icon: const Icon(Icons.remove),
onPressed: () {
setState(() {
_number--;
});
},
),
],
),
),
],
),
),
),
);
},
),
),
);
}
}