Unable to scroll ListView even with ScrollPhysics - flutter

I have a screen where I need to search for a term from the Appbar, and the area below shows a Card with selections from the displayed list, and the area below that will show all the results returned, within a scrollable list.
The problem is that although the items returned are placed in a ListView.builder and ScrollPhysics is on, the list is not scrollable. If I click on the Card and try to drag, it scrolls for a bit. But one cannot drag by clicking on the list, or items in it.
import '...';
class DiagnosisAdd extends StatefulWidget {
#override
_DiagnosisAddState createState() => _DiagnosisAddState();
}
class _DiagnosisAddState extends State<DiagnosisAdd> {
TextField searchBar;
TextEditingController searchTextController;
Network connection;
List<ICDCode> DiagnosisList;
List<ICDCode> selectedDiagnoses;
#override
void initState() {
connection = Network();
DiagnosisList = [];
selectedDiagnoses = [];
// searchBar = A widget
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: searchBar,
),
body: ListView(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
children: [
Card(
child: ListTile(
title: Text("Selected Diagnoses"),
subtitle: Wrap(
children: List.generate(
selectedDiagnoses.length,
(index) => Text(selectedDiagnoses[index].disease),
growable: true,
),
),
),
),
ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: DiagnosisList.length,
itemBuilder: (BuildContext context, int position) {
ICDCode codeDiagnosis = DiagnosisList[position];
return RaisedButton(
child:
Text('${codeDiagnosis.code}, ${codeDiagnosis.disease}'),
onPressed: () {});
},
)
],
),
);
}
Future searchDiagnosis(String text) async {
if (text.length < 3) {
return false;
}
var response = await connection.searchICDbyDisease(
searchString: text,
);
final jsonResponse = await json.decode(response);
List<ICDCode> diagnosis_list =
await jsonResponse.map<ICDCode>((i) => ICDCode.fromJson(i)).toList();
setState(() {
DiagnosisList = diagnosis_list;
});
}
}

You can't scroll your ListView because you have another ListView.builder() inside that ListView that can be scrolled. You would have to make your ListView.builder() unscrollable:
ListView.builder(
physics: NeverScrollableScrollPhysics(),
)
You cannot have two nested widgets that can scroll together at the same time. You would have to disable the nested widget from scrolling so that its the ListView that you scroll instead of ListView.builder()

ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: DiagnosisList.length,
itemBuilder: (BuildContext context, int position) {
ICDCode codeDiagnosis = DiagnosisList[position];
return RaisedButton(
child:
Text('${codeDiagnosis.code}, ${codeDiagnosis.disease}'),
onPressed: () {});
},
)
],
),
);

Related

i want to add a row in listview.builder but it goes blank

i want to add a row ( list of buttons that do filter the list), i tried wrapping listTile in column, listview.builde in column but it doesn't work. tried wrapping GetBuilder also but it doesn't work.
enter image description here
My Code :-
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quizzy/data_controller.dart';
import '../models/showQuestion.dart';
class AllQuestionBank extends StatefulWidget {
const AllQuestionBank({Key? key}) : super(key: key);
#override
State<AllQuestionBank> createState() => _AllQuestionBankState();
}
class _AllQuestionBankState extends State<AllQuestionBank> {
final DataController controller = Get.put(DataController());
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.getQuestionList();
});
return Scaffold(
appBar: AppBar(
title: const Text(' Question Bank'),
),
body: GetBuilder<DataController>(
builder: (controller) => controller.QuestionList.isEmpty
? const Center(
child: Text('😔 NO DATA FOUND (: 😔'),
)
: ListView.builder(
itemCount: controller.QuestionList.length,
itemBuilder: (context, index) {
return ListTile(
title: showQuestion(controller.QuestionList[index]),
);
}),
),
);
}
}
You could redefine your ListView as:
ListView.builder(
itemCount: controller.QuestionList.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return WhateverRowYouWant();
}
return ListTile(
title: showQuestion(controller.QuestionList[index - 1]),
);
}),
You can just define the scroll direction to be horizontal.
ListView.builder(
itemCount: controller.QuestionList.length,
scrollDirection: Axis.horizontal, <- added this line
itemBuilder: (context, index) {
return ListTile(
title: showQuestion(controller.QuestionList[index]),
);
}),
You can also find an example from the official docs here
Try this.
physics: NeverScrollableScrollPhysics(), shrinkWrap: true,
To add Row() on top you need Column() widget for sure
After that, you have to wrap ListView.builder() with the Expanded() widget this will help you
Ex.
return Scaffold(
body: SafeArea(
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {},
child: Text('Filter'),
),
],
),
Expanded(
child: GetBuilder<DataController>(
builder: (controller) => controller.QuestionList.isEmpty
? const Center(
child: Text('😔 NO DATA FOUND (: 😔'),
)
: ListView.builder(
shrinkWrap: true,
itemCount: controller.QuestionList.length,
itemBuilder: (context, index) {
return ListTile(
title: showQuestion(controller.QuestionList[index]),
);
},
),
),
),
],
),
),
);
Please, try this!!
This is the combination of GetxController and ListView with a top row I use:
class MyController extends GetxController {
var isRunning = true.obs; // set to isRunning.value = false; if done loading QuestionList
RxList<ProductModel> QuestionList = <ProductModel>[].obs;
}
Obx( () => controller.isRunning.isTrue
? 'Loading'
: ListView.builder(
itemCount: controller.QuestionList.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return Text('TOP ROW');
}
return ListTile(
title: showQuestion(controller.QuestionList[index - 1]),
);
}),
);

Flutter - Scroll ListView selected item into the center of the screen?

I have a horizontal scrolling ListView with an undetermined number of items inside.
How can I programatically scroll a specific item into the center of my screen?
Context: On the previous screen I have multiple items, and when I click on one, I need it to navigate to this screen and scroll the item selected on the previous screen to the center of the new screen.
My trouble is really just with the scrolling part.
Thanks in advance.
ListView:
final listViewController = ScrollController();
#override
Widget build(BuildContext context) {
return ListView.separated(
scrollDirection: Axis.horizontal,
physics: ClampingScrollPhysics(),
controller: listViewController,
padding: EdgeInsets.zero,
itemCount: testArray.length,
itemBuilder: (ctx, i) => Item(
testArray[i],
testArray[i] == 'item5' ? true : false,
() => {
// testing code for the scroll functionality
listViewController.animateTo(
i + MediaQuery.of(context).size.width / 2,
duration: Duration(seconds: 1),
curve: Curves.easeIn),
},
),
separatorBuilder: (ctx, i) => Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
),
);
}
}
Item Widget:
class Item extends StatelessWidget {
final String itemName;
final bool selectedItem;
final VoidCallback navigationHandler;
Item(
this.itemName, this.selectedItem, this.navigationHandler);
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
child: TextButton(
onPressed: navigationHandler,
child: Text(
itemName,
style: selectedItem
? Theme.of(context).textTheme.headline6?.copyWith(
fontSize: 22,
)
: Theme.of(context).textTheme.headline6?.copyWith(
color: Color(0xff707070),
),
),
),
);
}
}
The best solution to this issue that I've found is to use the package scrollable_positioned_list which can scroll to items based on its index.
If you knew the extent of its children you could have used a FixedExtentScrollController as the controller of your lisview and would not have needed to rely on a external dependency.
The gist of using the package is just to create a controller , this time an
ItemScrollController and just replace your ListView.separated to ScrollablePositionedList.separated
final ItemScrollController itemScrollController = ItemScrollController();
ScrollablePositionedList.separated(
itemScrollController: itemScrollController,
...
);
One then can scroll to a particular item with:
itemScrollController.scrollTo(
index: 150,
duration: Duration(seconds: 1),
curve: Curves.easeIn);
A complete example would be as follows
final testArray = [for (var i = 0; i < 100; i++) 'item$i'];
class _MyAppState extends State<MyApp> {
final itemScrollController = ItemScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: MyApp._title,
home: Scaffold(
body: ScrollablePositionedList.separated(
itemCount: testArray.length,
scrollDirection: Axis.horizontal,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.zero,
itemBuilder: (context, i) => Item(
testArray[i],
testArray[i] == 'item5' ? true : false,
() => {
// testing code for the scroll functionality
itemScrollController.scrollTo(
index: (i + 5) % testArray.length,
duration: Duration(seconds: 1),
curve: Curves.easeIn,
alignment: 0.5),/// Needed to center the item when scrolling
},
),
itemScrollController: itemScrollController,
separatorBuilder: (ctx, i) => Padding(padding: EdgeInsets.symmetric(horizontal: 6)),
)));
}
}
By the way be accustomed at whenever you're working with controllers create them in the State of a Stateful widget, so they are only created once, and dispose them if necessary. I'ts not the case with ItemScrollController but ScrollController would have needed to be disposed .

Flutter resizeToAvoidBottomInset true not working with Expanded ListView

The keyboard hides my ListView (GroupedListView). I think it's because of the Expanded Widget.
My body:
Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GroupedListView<dynamic, String>(
controller: _scrollController,
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemBuilder: (context, message) {
return ListTile(
title: ChatBubble(message),
);
},
elements: messages,
groupBy: (message) => DateFormat('MMMM dd,yyyy')
.format(message.timestamp.toDate()),
groupSeparatorBuilder: (String groupByValue) =>
getMiddleChatBubble(context, groupByValue),
itemComparator: (item1, item2) =>
item1.timestamp.compareTo(item2.timestamp),
useStickyGroupSeparators: false,
floatingHeader: false,
order: GroupedListOrder.ASC,
),
),
),
WriteMessageBox(
group: group,
groupId: docs[0].id,
tokens: [widget.friendToken])
],
);
Why the resizeToAvoidBottomInset isn't working?
I have opened an issue to the Flutter team
In short: use reversed: true.
What you see is the expected behavior for the following reason:
ListView preserves its scroll offset when something on your screen resizes. This offset is how many pixels the list is scrolled to from the beginning. By default the beginning counts from the top and the list grows to bottom.
If you use reversed: true, the scroll position counts from the bottom, so the bottommost position is 0, and the list grows from bottom to the top. It has many benefits:
The bottommost position of 0 is preserved when the keyboard opens. So does any other position. At any position it just appears that the list shifts to the top, and the last visible element remains the last visible element.
Its easier to sort and paginate messages when you get them from the DB. You just sort by datetime descending and append to the list, no need to reverse the object list before feeding it to the ListView.
It just works with no listeners and the controller manipulations. Declarative solutions are more reliable in general.
The rule of thumb is to reverse the lists that paginate with more items loading at the top.
Here is the example:
import 'package:flutter/material.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: 30,
reverse: true,
itemBuilder: (context, i) => ListTile(title: Text('Item $i')),
),
),
const TextField(),
],
),
),
);
}
}
As for resizeToAvoidBottomInset, it does its job. The Scaffold is indeed shortened with the keyboard on. So is ListView. So it shows you less items. For non-reversed list, gone are the bottommost.
It looks like you want the GroupedListView to be visible from the last line. The WriteMessageBox is pushed up by the keyboard and obscures the last messages. The most direct solution is to scroll the list to the bottom when the keyboard is visible. That is, when the WriteMessageBox gains focus.
Add a FocusScope to the WriteMessageBox in the build() method. It becomes
FocusScope(
child: Focus(
child: WriteMessageBox(),
onFocusChange: (focused) {
if (focused) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
)
)
Screenshot:
Code:
You can use MediaQueryData to get the height of keyboard, and then scroll the ListView up by that number.
Create this class:
class HandleScrollWidget extends StatefulWidget {
final BuildContext context;
final Widget child;
final ScrollController controller;
HandleScrollWidget(this.context, {required this.controller, required this.child});
#override
_HandleScrollWidgetState createState() => _HandleScrollWidgetState();
}
class _HandleScrollWidgetState extends State<HandleScrollWidget> {
double? _offset;
#override
Widget build(BuildContext context) {
final bottom = MediaQuery.of(widget.context).viewInsets.bottom;
if (bottom == 0) {
_offset = null;
} else if (bottom != 0 && _offset == null) {
_offset = widget.controller.offset;
}
if (bottom > 0) widget.controller.jumpTo(_offset! + bottom);
return widget.child;
}
}
Usage:
final ScrollController _controller = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ListView')),
body: HandleScrollWidget(
context,
controller: _controller,
child: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (_, i) => ListTile(title: Text('Messages #$i')),
),
),
TextField(decoration: InputDecoration(hintText: 'Write a message')),
],
),
),
);
}
It appears that you are using text fields so it hides data or sometimes it may overflow borders by black and yellow stripes
better to use SingleChildScrollView and for scrolling direction use scrollDirection with parameters Axis.vertical or Axis.horizontal
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child :Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GroupedListView<dynamic, String>(
controller: _scrollController,
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemBuilder: (context, message) {
return ListTile(
title: ChatBubble(message),
);
},
elements: messages,
groupBy: (message) => DateFormat('MMMM dd,yyyy')
.format(message.timestamp.toDate()),
groupSeparatorBuilder: (String groupByValue) =>
getMiddleChatBubble(context, groupByValue),
itemComparator: (item1, item2) =>
item1.timestamp.compareTo(item2.timestamp),
useStickyGroupSeparators: false,
floatingHeader: false,
order: GroupedListOrder.ASC,
),
),
),
WriteMessageBox(
group: group,
groupId: docs[0].id,
tokens: [widget.friendToken])
],
);
);
Please try this solution. Hope it will work for you. Thanks.
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GroupedListView<dynamic, String>(
scrollDirection: Axis.vertical,
shrinkWrap: true,
controller: _scrollController,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemBuilder: (context, message) {
return ListTile(
title: ChatBubble(message),
);
},
elements: messages,
groupBy: (message) =>
DateFormat('MMMM dd,yyyy').format(message.timestamp.toDate()),
groupSeparatorBuilder: (String groupByValue) =>
getMiddleChatBubble(context, groupByValue),
itemComparator: (item1, item2) =>
item1.timestamp.compareTo(item2.timestamp),
useStickyGroupSeparators: false,
floatingHeader: false,
order: GroupedListOrder.ASC,
),
),
),
WriteMessageBox(
group: group, groupId: docs[0].id, tokens: [widget.friendToken])
In short: use reversed: true, jump the scrolling position to 0.
final scrollController = ScrollController();
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (scrollController.hasClients) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
});
}
Widget _buildScrollView(){
return SingleChildScrollView(
reverse: true,
controller: scrollController,
child: [....],
);
}

Flutter pageview, add new elements at runtime

In my project I have a Pageview which contains widgets from a list. In runtime I add more elements to this list to show it in the Pageview. If I don't specify itemCounter than I can run out of index but I can see the new pages, however, if use itemCounter, the new page will not appear. For the itemCounter I use the length of my list. How can I add new elements runtime to a pageview?
In this case I can add new elements, but I can run out of index:
child: PageView.builder(
controller: pageController,
//itemCount: _dummy.length,
itemBuilder: (context, position) {
return _dummy[position];
},
),
),
This is case the new pages doesn't even show up. It's like the itemCounter doesn't change.
child: PageView.builder(
controller: pageController,
itemCount: _dummy.length,
itemBuilder: (context, position) {
return _dummy[position];
},
),
),
in this widget, when you clickfab it will create another page in PageView.
import 'package:flutter/material.dart';
class PageViewOnRuntime extends StatefulWidget {
PageViewOnRuntime({Key? key}) : super(key: key);
#override
_PageViewOnRuntimeState createState() => _PageViewOnRuntimeState();
}
class _PageViewOnRuntimeState extends State<PageViewOnRuntime> {
final PageController pageController = PageController();
int numberOfPage = 2;
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
controller: pageController,
itemCount: numberOfPage,
itemBuilder: (context, index) {
return Container(
color: index % 2 == 0 ? Colors.cyanAccent : Colors.blueGrey,
alignment: Alignment.center,
child: Text(index.toString()),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
numberOfPage++;
});
},
child: Icon(Icons.add),
),
);
}
}
let me know , if you need something else .

Flutter listview builder Scroll controller listener not firing inside list view?

I have a listview builder widget inside another list view. Inner listview listener is not firing when scrolling position reaches to its end.
initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.maxScrollExtent ==
_scrollController.position.pixels) {function();}
}
Container(
child: Listview(
children: <Widget>[
Container(),
ListView.builder(
controller: _scrollController,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Container();
},
),
]
)
)
You might have SingleChildScrollView attached before any widget :
so attach _scrollController to singleChildScrollView not listview
body: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
_chips(),
SizedBox(
height: 10,
),
_slider(),
_showGrid(),
],
),
),
the list view must scroll otherwise it won't work. Not only you have to remove the NeverScrollableScrollPhysics() but also add that list view into some container and set its height smaller then overall height of your ListView. Then the listView begin to scroll and the function will be triggered
ScrollController _scrollController = ScrollController();
List<int> list = [1, 2, 3, 4, 5];
initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.maxScrollExtent ==
_scrollController.position.pixels) {
print('firing');
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: ControlBar(
title: Text('Home'),
),
),
body: ListView(
children: <Widget>[
Container(
height: 150,
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(list[index].toString()));
},
),
),
],
),
);
}