Keep button fixed in the bottom of screen, but only when the above list height is shorter than the device height - flutter

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}
});

Related

Flutter ListView entries cut off when scrolled down

A screen in my app displays a list of "listings" the user has made. In the event there is not enough listings to fill the whole screen, and the user scrolls down, the listing they are scrolling will get cut off instead of being scrolled down.
example before scrolling
example after scrolling
As the user scrolls the list entry downwards I would like it not to disappear. I've tried wrapping the listview.builder in containers / sized boxes to define their size, although this behavior persists. I thought I could also put in some blank entries into the list that wouldn't respond to user clicks but that seems like an inefficient solution. Here is my code:
return Scaffold(
key: listing_home_scaffold_key,
resizeToAvoidBottomInset: false,
backgroundColor: BODY_BACKGROUND_COLOR,
appBar: returnAppBarForHomePages(context, "Listings", widget.local_user, updateUserState,
widget.local_user.usingDefaultImage, widget.local_user.getProfilePicURL, listing_home_scaffold_key),
drawer: settingsDrawer(widget.local_user),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
returnScrollableListView()
]),
);
Widget returnScrollableListView() {
return RefreshIndicator(
child: ListView.builder(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
itemCount: user_listings.length,
shrinkWrap: true,
padding: const EdgeInsets.only(top: 0),
itemBuilder: (context, index) {
return UserListing(listing: user_listings[index]);
}),
onRefresh: () async {
var cancel_loading_indicator = showLoadingIndicatorAllowClick();
await reQueryData();
cancel_loading_indicator();
},
);
}
Edit: Having the refresh indicator be the direct parent to the listview caused this problem. Moving it up a level so it's instead the parent of the Column fixed this.
Having the refresh indicator be the direct parent to the listview caused this problem. Moving it up a level so it's instead the parent of the Column fixed this.

ListView Builder vs SingleChildScrollView + Row combo

I want to make a responsive UI in my app. In my home page, I have ScrollView->Column->childrens structure.
When I want to use horizontal Listview.builder in my column, it throws me error because height is unbounded. I fix it with wrapping my listview builder with container and I give this container a height value. But I dont want to give this height hardcoded,
I want to make my container height to its own child's height. I searched it and found a solution like ScrollView->Row->list.generate instead of ListView.Builder. But is it okay to make it like this? Does it cause performance problems or is it a bad practice?
My list isn't big. It has max 20 elements
For example this throwing error:
#override Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: 5,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemBuilder: (context, index) {
return Container(
width: 200,
height: 200,
);
},
)
],
)),
); }
But this does not throw an error
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(
5,
(index) => Container(
width: 200,
height: 200,
color: Colors.red,
)),
),
),
],
));
}
To clarify for everyone:
ListViews generate their children "lazily" - meaning they won't be drawn until they have been shown on screen. This means, of course, that they do not know the height of their items.
For example, if the ListViews children have heights similar to this list:
ooooOooo
But only this part was shown on the screen:
oooo | Oooo
And the ListView set it's height to fit "o" then it wouldn't be able to fit "O" when it eventually came on screen.
On the other hand, Rows do draw all of their children on spawn, meaning that, while they do know the size for all their widgets, they can become very slow quickly.
I would not suggest using images inside rows with 2-digit+ children, as they can be laggy not only on their initial draw, but also while the user does other things in the page containing said row - things such as scrolling up/down the page.
While testing I found that a row with just 30 children of the same stack (a small AssetImage on top of an IconImage) would lag the entire page when just scrolling up/down - not even scrolling along the row itself.
My recommended solution for you Ahmet, even though you don't want to hard-code your ListView's height, is to settle and set your ListView's height using:
MediaQuery.of(context).size.height * (percentage of screen's height you'd like for the ListView to take).

How to dynamically increase the height of a list and scroll after reaching a max height?

Hi i'm trying to create a list view to which i dynamically add items in the app. I want the height of the list to grow till it reaches a defined max height, and then show a scrollbar when it overflows.
_buildPage() {
return Container(
width: widget.width,
constraints: BoxConstraints(
maxHeight: widget.maxHeight,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildTitle(),
_buildAddBump(),
ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => _buildItem(_items[index]),
),
],
),
);
}
This is of course not working since a ListView needs a pre-defined height. But wrapping the ListView in an Expanded will take all the remaining height and the ListView will be at maxHeight even when the items in it don't need the height.
I've tried putting the items in a SingleChildScrollView with a Column as a child, but that doesn't seem to do anything. The widget just overflows when i add more items than the height can hold. Is there a way to give a max height constraint to a list and tell it to take the height of its contents till it reaches the max height?
UPDATE:
I tried to add shrinkWrap: true to the ListView, but it's still not working. The page renders without errors and the ListView height increases as items are added, but when the height goes beyond maxHeight, it just overflows instead of adding a scroll.
Seems like I found the solution and it's quite easy. Wrap your ListView inside Flexible and set shrinkWrap to true:
...
Flexible(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => _buildItem(_items[index]),
shrinkWrap: true,
)
),
...
If it's not the solution, let me know what is wrong in comments.

Persistent draggable bottom sheet in Flutter

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),
));
},
),
);
},
),
),
],),

How do I implement this in Flutter?

I have tried BottomNavigationBar but it requires an Icon.
This instead is text only, sort of represents analytics history and it's scrollable.
For the bottom bar you could make your own custom widget like this:
Container(
height: 80.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: List.generate(10, (int index) {
return new Text("$index");
}),
),
),
Replace the children with actual children that you want for the ListView and adjust its wrapping to have a height that suits your need and there you go