I am working on my first Flutter app (in Android Studio) and I have wasted many hours trying to accomplish the following (including reading every StackOverflow post and webpage I could find).
My screen has three sections in a Column, that is within a SingleChildScrollView. The SingleChildScrollView is necessary for two reasons: to prevent overflow when the device keyboard opens, and to allow the page to scroll when the third section contains more items than will fit on the screen (note: I don’t want the ListView in the bottom section to scroll on its own, because on small screens very few items can appear and the UI is not comfortable in that situation, so I’d rather the whole page scrolls).
This is what I am trying to accomplish: When there are few or no items in the ListView, I simply want the grey background color to extend to the bottom of the screen, so that there is no separate white area there (which looks bad). (Alternatively, how can I "fill" the white space at the bottom with the same background color. This would get the desired effect as well. I tried this in many ways as well without success!)
On the other hand, when there are many items, I want the ListView to extend as much as needed to show all of them (with the page scrollable accordingly).
See the screenshots:
Page scrolling is necessary in some cases
I’ve tried every combination of surrounding widgets, both high in the tree and within it. I’ve tried calculating the height of the screen and setting the height of the bottom area "manually". I’ve read everything I could find on the topic and did not manage to find a solution: either the visual appearance is not what I want, or there are screen overflow errors, or unbounded constraint errors.
Your solution will be appreciated! Thanks in advance.
Here is the page code:
import 'package:flutter/material.dart';
late int countOfItems;
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
@override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
appBar: AppBar(
title: const Text("This App Title"),
leading: GestureDetector(
onTap: () {
/* Write main menu listener code here */
},
child: const Icon(
Icons.menu,
),
),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: [
// Column for rest of screen below appbar
// Header Section
Container(
padding: const EdgeInsets.symmetric(vertical: 33, horizontal: 25),
// alignment: Alignment.bottomCenter,
height: 140,
decoration: BoxDecoration(color: Colors.blue.shade800),
child: const Row(
// Top main area row
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
// Top main area column
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Your current balance",
style: TextStyle(fontSize: 17, color: Colors.white),
),
SizedBox(
height: 10,
),
Text(
"$250.00",
style: TextStyle(
fontSize: 36,
color: Colors.white,
fontWeight: FontWeight.bold),
),
]),
]),
),
const SizedBox(
// Vertical space above New Entry area
// Separates between top static section and New Entry section
height: 25,
),
Container(
// New Entry area
padding: const EdgeInsets.symmetric(horizontal: 25),
child: const Row(
// New entry title
children: [
Text(
"New entry",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold),
)
], // children
),
),
const SizedBox(
height: 25,
),
Column(
// New Entry area: two rows: three icons buttons row + input row
children: [
const Row(
// Three entry option buttons (icon + label)
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
"Opt 1",
style: TextStyle(
color: Colors.black,
fontSize: 28,
fontWeight: FontWeight.bold),
), //
Text(
"Opt 2",
style: TextStyle(
color: Colors.black,
fontSize: 28,
fontWeight: FontWeight.bold),
), // Gave
Text(
"Opt 3",
style: TextStyle(
color: Colors.black,
fontSize: 28,
fontWeight: FontWeight.bold),
), // Spent
]),
Container(
// Input line (label + input + icon)
padding:
const EdgeInsets.symmetric(vertical: 33, horizontal: 25),
child: const Row(
// Three sub-elements: label + input + icon
children: [
Padding(
padding: EdgeInsets.fromLTRB(20, 8, 20, 8),
child: Text(
"Enter amount:",
style: TextStyle(fontSize: 20),
),
),
Expanded(
// Give the most space in this row to the text input field
child: TextField(
maxLength: 12,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 26),
decoration: InputDecoration(
hintText: '0.00',
counterText: "",
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(15, 0, 10, 0),
child: IconButton(
icon: Icon(Icons.check_circle),
color: Colors.blue,
iconSize: 40.0,
onPressed: (null),
),
),
], // Row children array
),
),
]),
Container(
// Recent activity area
padding: const EdgeInsets.fromLTRB(25, 20, 25, 0),
decoration: BoxDecoration(color: Colors.grey.withOpacity(0.2)),
child: const Column(
// contains title + ListView
mainAxisSize: MainAxisSize.max,
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
"Recent items",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 15,
),
TestListView(),
SizedBox(
height: 10,
),
]),
),
]),
),
);
}
}
class TestListView extends StatefulWidget {
const TestListView({Key? key}) : super(key: key);
@override
State<TestListView> createState() => _TestListViewState();
}
class _TestListViewState extends State<TestListView> {
@override
void initState() {
super.initState();
_getItems(); // Retrieve some recent activities to show on the main screen
}
void _getItems() {
// countOfItems = -1;
// countOfItems = 0;
// countOfItems = 3;
countOfItems = 10;
setState(() {});
}
@override
Widget build(BuildContext context) {
// This builds the ListView of items or alternative message
if (countOfItems == -1) {
// Show Loading spinner
return const Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.fromLTRB(0, 28, 0, 28),
child: CircularProgressIndicator(),
),
);
} else if (countOfItems == 0) {
// Show No Items message
return const Padding(
padding: EdgeInsets.fromLTRB(0, 28, 0, 28),
child: Text(
"No items to show",
style: TextStyle(color: Colors.black, fontSize: 16),
),
);
}
// If reached here, return items in a ListView
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
separatorBuilder: (BuildContext context, int index) => Divider(
color: Colors.grey.withOpacity(0.2),
endIndent: 10,
indent: 10,
),
itemCount: countOfItems,
itemBuilder: (context, index) {
return ListTile(
title: Text("ListTile $index"),
titleTextStyle:
const TextStyle(fontWeight: FontWeight.w500, color: Colors.black),
visualDensity: const VisualDensity(horizontal: 0, vertical: -4),
);
},
);
} // if
}
2
Answers
In your Container for the ‘recent items’ section, add the following line:
It should make it take as much space as it can.
Admittedly it is not a very sophisticated solution, but it works:
Wrap all the widgets that constitute the ‘New entry’ section of the screen in a
Column
, then wrap thisColumn
in aContainer
. Set thecolor
property of thisContainer
to white or whatever color you want it to be:Then set the
backgroundColor
property of yourScaffold
and thecolor
property of theContainer
that contains youListView
to the same color. If you want to achieve the current look useColors.grey[200]
. Do not user colors with opacity.