When using FloatingActionButton in conjunction with a ListView, it hides part of the UI of the ListView entries, as seen in the screenshot.
Is there a way to automatically add extra spacing at the bottom of a ListView so that the + button does not hide any content?
Assuming you are using ListView, there is a very nice and simple solution to this.
You probably do not want to add padding around the ListView itself as that would make some area of your UI unused.
ListView has apadding parameter for exactly this use case. You can essentially add some padding to the bottom of your ListView that is part of the scrollable area. This means that you will only start seeing this padding once you have scrolled all the way to the bottom. This will allow you to drag the last few items above the FloatingActionButton.
To find an appropriate value for the padding, you can make use of kFloatingActionButtonMargin and _kExtendedSizeConstraints, which is not accessible, thus, I will just use 48 like this.
This means that you will want to add the following to your ListView:
ListView(
padding: const EdgeInsets.only(bottom: kFloatingActionButtonMargin + 48),
..
)
Here is a working example:
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
home: Scaffold(
body: ListView.builder(
padding: const EdgeInsets.only(bottom: kFloatingActionButtonMargin + 48),
itemCount: 23,
itemBuilder: (context, index) => ListTile(
trailing: Text('$index'),
)),
floatingActionButton: FloatingActionButton(
onPressed: () {},
),
)));
}
Expandable(
ListView.builder(
...
padding: EdgeInsets.only(bottom: 200),
...
)
)
Related
I'm having a bit of annoyance. I want a scroll bar present in my flutter web 'page' at all times, to indicate to the user that there's still some elements to view them. And I achieved this using scrollbar isAlwaysShown to true in my Theme class.
Now, when I use GridView.builder to generate elements on the screen, I must provide height constraints beforehand, or else I'll get an error that says 'height is infinite'.
The problem with this is, there are 2 scroll bars visible at all times. One from the SingleChildScrollView, and one from the GridView.builder. I need the SingleChildScrollView with the column so I can have a footer at the bottom of the page.
My question is, how can I get rid of the GridView.builder scroll bar?
Thanks in advance...
My code:
Scaffold(
key: _scaffoldKey,
appBar: AppBar(),
drawer: const DrawerWidget(),
body: SingleChildScrollView(
child: Column(
children: [
Container(
constraints: BoxConstraints(maxHeight: _size.height),
margin: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(vertical: 10)
: null,
padding: Responsive.isDesktop(context)
? desktopPadding
: smallAndMediumPadding,
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemCount: 12,
itemBuilder: (BuildContext context, int index) {
return GridCard(name: '', image: '');
}
)
),
/////////////// Footer ///////////////////
Footer()
]
)
)
)
Actually you can remove the BoxConstraints from your Container and to solve the height issue you have a property in GridView.builder named shrinkWrap. You just need to set it true and it will solve your height issue.
shrinkWrap: true,
and after that you may have issue with Scrolling the GridView.builder so to solve that use another property primary and set it to false
primary : false,
You shouldn't use multiple Scrollables inside each other to get footer functionality. By using shrinkWrap on the inner scrollable (GridView) you'd effectively turn it into a regular unscrollable widget and use scrolling of the outer view (SingleChildScrollView). It is terrible for performance because Flutter would have to build, layout and (probably) render entire GridView including all the items every time.
You should instead use CustomScrollView to display your grid and footer like this:
Scaffold(
body: CustomScrollView(
slivers: [
SliverPadding(
// not sure what you were trying to do with your padding
padding: (Responsive.isDesktop(context)
? const EdgeInsets.symmetric(vertical: 10)
: EdgeInsets.zero) +
(Responsive.isDesktop(context)
? desktopPadding
: smallAndMediumPadding),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
delegate: SliverChildBuilderDelegate(
(context, index) => GridCard(name: '', image: ''),
childCount: 12,
),
),
),
SliverToBoxAdapter(
child: Footer(),
),
],
),
)
I need to make a scrollable for all content for partiular page, but the problem is that I need to have dynamic list at the middle, which grows. So I use CustomScrollView. From top to bottom at slivers[] I have SliverToBoxAdapter decorated as I need, then SliverList and then another one SliverToBoxAdapter. This totaly what I need it is scrollable all together, byt decoration problem with SliverList. So i need to wrap somehow SliverList with the same style so it looks like content inside SliverToBoxAdapter (Borders, evevation, background etc). But I cant understand how to achieve this. The CustomScrollView accepts only Slivers..
I tried to put List inside SliverToBoxAdapter but it scrolls separately or...
Ive solved it but I think in comletely wrong way, with wrapShrink to True, but the Docs day it is very expensive.. And also weird because i moreover need to block scroll Physics with NeverScrollableScrollPhysics(). So looking for better solution
Maybe I do not need to use Slivers?
The general problem is I need vertically lets say Container then List (growable) and then another Container under the list, no matter how long the list are.
Like this
container
List with ability to
dynamically grow when user
taps button
Container strongly under
the List with button
by tapping which user add
the element to the List
Any solutions, ideas, advices ...
Thanks for attention)
Code for sake of simplicity
The BoxAdapter and SliverList
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Center(
child: Container(
child: Column(
children: [
Characteristics(),
],
),
),
),
),
// this is how I have done
// but do not like not lazy..
// if put SliverList directly cant decorate it..
SliverToBoxAdapter(
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (contex, index){
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('Grid Item $index'),
);
},
itemCount: 20,
)
),
],
),
I am developing a Flutter app and would like to keep my button always in the bottom of the screen UNLESS what is in top of it is high enough to make the screen scroll. Examples:
Example A (what is on top of the button does not have height enough to scroll the screen)
Example B (what is on top of the button has height enough to scroll the screen, so the button will just go offscreen and allow the screen to scroll)
So summarizing: the button should be forced to stay at the bottom of the screen when the rest of the list is shorter than the device height, but if the list height is greater than the device height the button should behave normally and stay below the list.
What I tried:
Using a ListView, which will normally scroll if necessary, but I couldn't find a way to send the button to the botton of the screen;
Using a Column. With the help of a Spacer I could make the button go to the bottom of the screen, but a Column will not scroll, and if I add a SingleChildScrollViewto wrap it, the Spacer will no longer work because SingleChildScrollView has the potential to have infinite height;
Thanks.
I got it. The right way of doing this is using LayoutBuilder, ConstrainedBox, IntrinsicHeight and Expanded, like this:
(In this example, _widgets is a list of widgets that I want it to be on top. _buttons is a list of buttons that I want to be in the bottom)
return Scaffold(
appBar: buildAppBar(),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
Expanded(
child: Column(
children: _widgets,
),
),
Column(
children: _buttons,
)
],
),
),
),
);
},
),
);
The documentation says to avoid this, though, as it can be expensive. You can read more about this solution and other details in the documentation.
Stack(
children:[
//Use list widget,
yourList.length<7? Postioned(
bottom :10, margin from bottom
child: //button widget):Container()
])
In listbuilder
ListView.builder(
physics: ScrollPhysics(),
itemCount: yourList+1,
itemBuilder: (BuildContext ctxt, int index) {
if (index== yourList.length) {
return yourList.length >7? //your button widget:Container();
} else {
return //list item}
});
I'm using Rubber library at the moment, do you know an approach without using 3rd parts libraries?
The Bottom Sheet must be persistent (not dismissable, not triggered by any button instead, always displayed) and draggable (It must be expanded and collapsed by dragging gestures)
Perhaps DraggableScrollableSheet could work?
I haven't yet tried it out myself, but maybe you could fiddle with a listview to make it work.
I'm guessing something like having it's child be a listview, and then limit both the max child size and the maximum scroll extent
If you do not care that the bottom sheet must snap to different positions you can use the widget from the following package (snapping_sheet) I made.
Or, if you do not want to use it as a 3rd part library, you can copy the code and use it as your own widget from repository here: Github - Snapping sheet
Use DraggableScrollableSheet. Here's an example:
Stack(
children: [
Container(), //the page under the DraggableScrollableSheet goes here
Container(
height: MediaQuery.of(context).size.height,
child: DraggableScrollableSheet(
builder: (BuildContext context, myscrollController) {
return Container(
color: Colors.blue,
child: ListView.builder(
controller: myscrollController,
itemCount: 40,
itemBuilder:(BuildContext context, int index) {
return ListTile(title: Text('Item $index',
style: TextStyle(color: Colors.black),
));
},
),
);
},
),
),
],),
I have a ListView, at the top of which I have a map, I want the map to scroll out of view when the ListView is scrolled, but I also want the user to be able to interact with the map. So scrolling should only happen when the user scrolls on the other ListView widgets and not when they scroll on the map, then I want the gesture to be applied directly to the map.
Currently though, when the user scrolls on the map, it scrolls the whole ListView.
I have tried the other suggestion that I have come across on here
In Flutter, how can a child widget prevent the scrolling of its scrollable parent?
I added a GestureDetector as suggested in the answer to the post above wrapping the map container in the sample below, however this just blocked scrolling of both the ListView and the Map when scrolling on the map. Video link https://imgur.com/SeCRzUC
Here is the widget that is returned by my build method. This code depends on the google_maps_flutter plugin.
Container(
height: MediaQuery.of(context).size.height,
child:
ListView.builder(
itemCount: 12 + 1,
itemBuilder: (context, index) {
if (index == 0) return GestureDetector(
onVerticalDragUpdate: (_){},
child: Container(
height: MediaQuery.of(context).size.height / 2,
child: GoogleMap(initialCameraPosition: initalPosition),
),
);
else return ListTile(title: Text("$index"),);
}
)
),
I had hoped the map would capture gestures but it doesn't, the listview which contains it captures all. Could anyone suggest how I could force all gestures for this item in the list to be passed directly to the map, and still have the list scroll when other items in the list are scrolled?
The accepted answer (Eset Mehmet's answer was marked as excepted as of time of writing) is way to complicated and in my case it did not even work! It provided left-to-right scrolling, panning, and scaling, however top-to-bottom scrolling still scrolled the ListView.
A real solution is very simple, since GoogleMap by default has those gesture detectors. You must just specify that gesture detection must be prioritized by GoogleMap and not ListView. This is achieved by giving GoogleMap object an EagerGestureRecognizer in the following way for example.
ListView(
children: <Widget>[
Text('a'),
Text('b'),
GoogleMap(
...,
gestureRecognizers: {
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
),
],
)
In this way, all gestures that happen on or over the GoogleMap object will be prioritized by GoogleMap instead of any other widget.
Edit: campovski 's answer down below is the updated answer.
Depreciated Answer:
Wrap everything with ListView if you want to move out GoogleMap widget from the screen when scrolling.
Override ListView scrolling phyics with GoogleMap gestureRecognizers.
Disable ListView.builder scrolling physics due to conflict between ListView physics.
First import the dependencies:
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
build method:
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: GoogleMap(
initialCameraPosition:
CameraPosition(target: LatLng(41, 29), zoom: 10),
gestureRecognizers: Set()
..add(
Factory<PanGestureRecognizer>(() => PanGestureRecognizer()))
..add(
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer()),
)
..add(
Factory<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer()),
)
..add(
Factory<ScaleGestureRecognizer>(
() => ScaleGestureRecognizer()),
),
),
),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 12,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
},
)
],
),
);
}