Flutter ListView entries cut off when scrolled down - flutter

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.

Related

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).

Nested Listview not scrolling horizontally

I have a relatively complex Listview setup, where one Listview acts as a scrolling parent of a horizontal Listview, which acts as a parent to a third vertical Listview.
Here is an image of the general idea of the layout: https://cdn.discordapp.com/attachments/752981111738466467/895739370227773480/IMG_20211007_142732.jpg.
I'm having trouble getting the middle Listview, the horizontal Listview (marked 2 in the image), to scroll.
ConstrainedBox(
constraints: BoxConstraints(...),
child: ListView.builder( // This is Listview 1
controller: ScrollController(),
itemCount: itemCount,
itemBuilder: (context, worldIndex) {
return ConstrainedBox(
constraints: BoxConstraints(...),
child: ListView.builder( // This is Listview 2
controller: ScrollController(),
itemCount: ...,
scrollDirection: Axis
.horizontal, // grows horizontally to show the hierarchy of one card (the card selected in the hierarchy above it, or for the first level, the world) to its children
itemBuilder: (context, index) {
...
return ConstrainedBox(
constraints: BoxConstraints(...),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
decoration: ...,
),
child: ListView.builder( // This is Listview 3
controller: ScrollController(),
itemCount: getNumChildren(index),
itemBuilder: (context, index2) {
return ...;
}),
)));
}));
}));
For brevity, I have removed several parts of my code and replaced them with elipses. I don't think it is likely that any of these areas could cause any issues with the Listview, but please let me know if they could.
Edit: The code I have already works properly, aside from the horizontal Listview not scrolling. My solution needs to have a dynamically expandable Listview at each level of the tree (1, 2, and 3, in the image, for example). The primary target platform for this is Windows.
I'm assuming the issue involves a problem with which Listview wins the GestureArena, but I am currently at a loss in how to work around that issue, providing a way to scroll all of available Listviews.
Thank you in advance, and I hope you have a great day!
As I could understand it. Maybe you are looking for something like this? I have commented the 3 types of scrolls you need. If anything is not as expected, please mention.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
for (var i = 0; i < 5; i++)
// One big section in the largest col (1).
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 200.0,
child: ListView.builder(
// Horizontal item builder. (2)
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) {
return SingleChildScrollView(
child: Column(
// Inside one vertical section. (3)
children: [
for (var i = 0; i < 5; i++)
ElevatedButton(
onPressed: () {}, child: Text("OK")),
],
),
);
},
),
),
],
),
],
),
),
),
),
);
}
The solution to this turned out to be that Flutter has intentionally turned off the ability to scroll horizontally using both scroll wheel and dragging for Windows (or at least, that's what seems to be the case according to what I was able to find in this issue).
To solve that, they have made a migration guide here.
Following the guide, it is extremely simple to override the drag devices used in order to re-enable the intended drag scrolling. This still does not enable scrolling horizontally with a mouse wheel, but for my code that is not an issue.
class CustomScrollBehavior extends MaterialScrollBehavior { // A custom scroll behavior allows setting whichever input device types that we want, and in this case, we want mouse and touch support.
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
};
}
Then, in the actual Listview's code, we set a ScrollConfiguration with this new class as its behavior.
ScrollConfiguration(
behavior: CustomScrollBehavior(),
child: Listview.Builder(
controller: ScrollController(),
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
...
})

How to add other components additional to gridview on same page in flutter

...
if (snapshot.data == null) {
return Container(
child: Center(
child: Text("Loading..."),
));
} else {
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (context, index) => Card(
child: GridTile(
child: Center(
child: Text(snapshot.data[index].service_type),
),
)));
}
...
When this code is executed, the result is as follows,
In this, I need to add an image component on top of the grids. Please help me on achieving that. The below image is the expected result design.
Using shrinkWrap on big lists is a bad practice, you should wrap your GridView/ ListView with expanded when you want something above or below it, also don't nest scroll views like that. You should use CustomScrollView or NestedScrollView instead. What they said in the docs.
Shrink wrapping the content of the scroll view is significantly more expensive than expanding to the maximum allowed size because the content can expand and contract during scrolling, which means the size of the scroll view needs to be recomputed whenever the scroll position changes.

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

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

How to avoid scrolling listview when scrolling in map within listview

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