Maka
Maka

Reputation: 663

Flutter ListView height and Staffold extendBody: true

I have came across one "issue" with using ListView builder (multiple of them) on single page. In combination with CustomScrollView and SliverToBoxAdapter I was able to build 3 different ListVIew-s with vertical and horizontal scrolls.

Besides that I have BottomNavigationBar which has transparent background and floating button docked in the middle.

Main Scaffold has "extendBody" flag set to true, which will expand content below BottomNavigationBar. Exactly what I wanted.

The issue and downside of using "extendBody" is that every ListView builder with vertical scroll will create extra padding below to compensate BottomNavigationBar height.

I have tried many things to eliminate that padding in each ListView widget that is placed on page.

Solution: Actual solution is posted below

Upvotes: 1

Views: 245

Answers (2)

Michał Klepaczko
Michał Klepaczko

Reputation: 66

You can set padding: EdgeInsets.zero in you ListView.builder

ListView.builder(
   padding: EdgeInsets.zero,
   ...
)

Upvotes: 0

Maka
Maka

Reputation: 663

I have spent some time on figuring out why is this actually happening and solution was simple, reason was Scaffold that expands it's Body and it's widget's with extra paddings.

Reason behind it

Material's design Scaffold core library scaffold.dart has _BodyBuilder class which has definition and returns LayoutBuilder widget. This Widget is defined as follows:

final double bottom = extendBody
          ? math.max(metrics.padding.bottom, bodyConstraints.bottomWidgetsHeight)
          : metrics.padding.bottom;

Which is activated if extendBody is set. For this reason, extra padding is added to each listview or gridview widget in widget tree.

Solution

Create a custom Scaffold (create separate class ex components/my_scaffold.dart) and add code below. With this custom widget class we override Body builder with CustomBody which has paddings removed. You can chage bottom and top heights if needed, for me padding of 0.0 works perfectly.

import 'package:flutter/material.dart';
// Use PreferredSizeWidget for appbar type, not just Widget
class CustomProdexScaffold extends StatelessWidget {
  final PreferredSizeWidget? appBar;
  final Widget body;
  final Widget? floatingActionButton;
  final FloatingActionButtonLocation? floatingActionButtonLocation;
  final Widget? bottomNavigationBar;
  final bool resizeToAvoidBottomInset;
  final bool extendBody;
  final bool extendBodyBehindAppBar;
  final Widget? drawer;

  const CustomProdexScaffold({
    Key? key,
    required this.body,
    this.appBar,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.bottomNavigationBar,
    this.resizeToAvoidBottomInset = true, // Set default to true
    this.extendBody = true, // Set default to true
    this.extendBodyBehindAppBar = false, // Set default to false
    this.drawer,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: appBar,
      body: CustomBody(child: body),
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      bottomNavigationBar: bottomNavigationBar,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawer: drawer,
    );
  }
}

class CustomBody extends StatelessWidget {
  final Widget child;
  final bool extendBody;
  final bool extendBodyBehindAppBar;

  const CustomBody({
    Key? key,
    required this.child,
    this.extendBody = true, // Set default to true
    this.extendBodyBehindAppBar = false, // Set default to false
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    if (!extendBody && !extendBodyBehindAppBar) {
      return child;
    }

    return _buildBodyBuilder(context, child);
  }

  Widget _buildBodyBuilder(BuildContext context, Widget child) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        final MediaQueryData metrics = MediaQuery.of(context);

        final double bottom = extendBody
            ? 0.0 // override to remove padding at the bottom
            : metrics.padding.bottom;

        final double top = extendBodyBehindAppBar
            ? 0.0 // override to remove padding at the top
            : metrics.padding.top;

        return MediaQuery(
          data: metrics.copyWith(
            padding: metrics.padding.copyWith(
              top: top,
              bottom: bottom,
            ),
          ),
          child: child, // No need to cast, use child directly
        );
      },
    );
  }
}

How to use it

Then import your components/my_scaffold.dart in your page and then use class as in example:

import 'components/my_scaffold.dart';

// Your page with main widget tree
Widget build(BuildContext context) {    
    return MyCustomProdexScaffold(  // replace original Scaffold with new
      resizeToAvoidBottomInset: true,
      extendBody: true, // set this if required - in my case I needed it
      extendBodyBehindAppBar: false,
      drawer: null, // you can define your Drawer, I don't need one
      appBar: AppBar(
        automaticallyImplyLeading: false,
        backgroundColor: AppColors.prodexBlue,
        title: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Transform.translate(
              offset: const Offset(-2, -3),
              child: Image.asset('assets/images/prodexlogo.png', fit: BoxFit.contain, height: 42, alignment: FractionalOffset.center),
            ),
          ],
        ),
        iconTheme: const IconThemeData(color: Colors.white),
      ),
      body: // .... Stack(  any widget that you want here

No all your widgets will have desired (in my case 0.00) paddings. Again this will affect GridView and ListView where it's more commonly used and visible.

I hope that this will help others!

Upvotes: 5

Related Questions