skip to Main Content

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:

The challenge

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


  1. In your Container for the ‘recent items’ section, add the following line:

    constraints:
            BoxConstraints(minHeight: double.infinity),
    

    It should make it take as much space as it can.

    Login or Signup to reply.
  2. 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 this Column in a Container. Set the color property of this Container to white or whatever color you want it to be:

    Container(
      color: Colors.white,
      child: Column(
        children: [
          const SizedBox(
            // Vertical space above New Entry area
            // Separates between top static section and New Entry section
            height: 25,
            ),
          Container(
            // New Entry area
          ...
    

    Then set the backgroundColor property of your Scaffold and the color property of the Container that contains you ListView to the same color. If you want to achieve the current look use Colors.grey[200]. Do not user colors with opacity.

    @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.grey[200],
          ...
    
              Container(
                // Recent activity area
                padding: const EdgeInsets.fromLTRB(25, 20, 25, 0),
                decoration: BoxDecoration(color: Colors.grey[200]),
                // Instead of decoration you can simply use:
                // color: Colors.grey[200],
                ...
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search