Reputation: 663
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
Reputation: 66
You can set padding: EdgeInsets.zero in you ListView.builder
ListView.builder(
padding: EdgeInsets.zero,
...
)
Upvotes: 0
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.
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.
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
);
},
);
}
}
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