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 .
Related
I want to start discussion here about the DraggableScrollableSheet and how to possibly use it with nested Navigator.
The problem is simple and problematic at the same time.
Here is an example code:
First I have an Draggable Scrollable with custom Stateful widget MyCustomTabs
DraggableScrollableSheet(
builder: (context, scrollController) => Column(
children: [
Expanded(child: MyCustomTabs(
key: myCustomTabsKey,
scrollController: scrollController,)),
],
),
),
Secondly this is an implementation of MyCustomTabs
class MyCustomTabs extends StatefulWidget {
final ScrollController scrollController;
const MyCustomTabs({Key? key, required this.scrollController})
: super(key: key);
#override
State<MyCustomTabs> createState() => MyCustomTabsState();
}
class MyCustomTabsState extends State<MyCustomTabs> {
int _page = 1;
set page(int value){
setState((){
_page = value;
});
print("set new great sttea");
}
#override
Widget build(BuildContext context) {
return Navigator(
pages: [
if(_page == 1)
MaterialPage(
child: ListView.builder(
controller: widget.scrollController,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
color: Color(0xFF41DACC),
),
),
)),
if(_page == 2)
MaterialPage(
child: ListView.builder(
//THIS WILL CREATE AN ERROR
controller: widget.scrollController,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
color: Color(0xFFAB27AF),
),
),
)),
],
);
}
}
This setup will cause an error if we try to attach scroll controller to different ListView widgets.
So Now I will try to solve this problems with use of notifications and my custom Scroll controllers pair.
One of which will send notifications about a scroll and the second will receive notifications from child widgets and pass it to DraggableScrollable
Or maybe there is another solution out of the box.
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]),
);
}),
);
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 .
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: [....],
);
}
I need to make a dynamic ListView height inside another ListView. None of the answers here I have come to didn't really answer it. I've made a simple example of what I'm trying to do so you can simply copy and paste it to try it and play with it. I've got problem with sub ListView where I need to make it grow or shrink based on number of items in it (problem commented in program)
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
List<List<bool>> subList = [
[true, true],
[true]
];
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
ListTile(
trailing: IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
subList[index].add(true);
});
},
),
title: Text(
'item $index',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
Container(
height: 100, // <--- this needs to be dynamic
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
),
)
],
);
},
separatorBuilder: (BuildContext context, int index) => Divider(),
itemCount: subList.length);
}
}
class TestRow extends StatelessWidget {
final String text;
final Function onRemove;
const TestRow({this.onRemove, this.text});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(text),
IconButton(
icon: Icon(Icons.remove),
onPressed: onRemove,
)
],
),
);
}
}
BTW I managed to make a workaround by changing height of container (commented part) to height: 50.0 * subList[index].length where 50 is height of sub title. I'm still looking for a proper way of doing it where I wouldn't need to hardcode height of the tile and calculate it
Here is video of the project with workaround how it should work
Try setting the shrinkWrap property to true and remove the container
ListView.builder(
shrinkWrap: true, //<-- Here
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
)
Output: