Flutter - ScrollController with CupertinoSliverNavigationBar, largeTitle to smallTitle not working - flutter

When I use a ScrollController in a ListView, it blocks the CupertinoSliverNavigationBar largeTitle from transitioning to a smallTitle. However, if I remove the scrollController, the problem goes away. I think it might be a bug in the Cupertino Library
This code demonstrates the issue:
ScrollController scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('Large Title'),
),
];
},
body: ListView.builder(
controller: scrollController,
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Center(child: Text('Entry ${index}')),
);
}),
),
);
}
Now if I remove the scrollController, the problem is gone:
ScrollController scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('Large Title'),
),
];
},
body: ListView.builder(
//controller: scrollController,
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Center(child: Text('Entry ${index}')),
);
}),
),
);
}

This is an expected behavior since NestedScrollView is meant to manage the scroll positions of all other child scrolling views:
NestedScrollView class
A scrolling view inside of which can be nested other scrolling views,
with their scroll positions being intrinsically linked.
So, you cannot control the positions of its children views with an individual ScrollController. However, you can provide one for NestedScrollView to manage all at once.
NotificationListener
There is still a way to listen to the scroll events of the inner scroll view besides using ScrollController. You can have your ListView inside of NotificationListener and check the scroll info that it provides.
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('Large Title'),
),
];
},
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent) {
print('Reached the bottom');
}
return;
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Center(child: Material(child: Text('Entry $index'))),
);
},
),
),
),
);
}

Related

Flutter disable scrolling on CupertinoNavBar with large title

How can I disable scrolling when there is a widget with CupertinoNavBar and large title? For instance I have:
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('Settings'),
)
];
},
body: Center(child: Text('Home Page'),),
),
);
}
if I add physics: NeverScrollableScrollPhysics(), it doesn't work

Is it possible to drag DraggableScrollableSheet programmatically in flutter?

I want to know how to drag the DraggableScrollableSheet widget to go up or down programmatically. For example, based on a timer class.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('DraggableScrollableSheet'),
),
body: SizedBox.expand(
child: DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[100],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
),
),
);
}
}

ListView.builder inside futurebuilder inside Listview

I wanted to to make my home page scrollable i mean i want to scroll everthing on the body so i made a list view and inside the list view there are other widgets and under those widgets i want to show a future builder that has another listview.builder in it but i dont want it to be scrolled alone i want it to be scrolled with the other widgets in the home screen
this is my home screen body:
body: SafeArea(
child: ListView(
children: <Widget>[
Search(),
FeaturedProducts(),
OnSaleProducts(),
],
),
),
OnSaleProducts() is the widget that has a futurebuilder in it this is the code
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
child: FutureBuilder(
future: getOnSaleProduct(),
builder: (_, snapshot){
if (snapshot.connectionState == ConnectionState.waiting) {
return ListView.builder(
itemCount: 4,
itemBuilder: (_, index) {
return Column(
);
});
}else return ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 9,
itemBuilder: (_, index) {
return InkWell(child: ProductCard(name: "new", price: 123, picture: '', onSale: true));
});
}));
}
Then you should not use a ListView inside OnSalesProduct() but a simple Column!
Column(children: List.generate(count, (int index) => widget(index))
Hopefully, what you are trying to know:
Scroll everthing on the body
Inside the scrollable view, want to show a future builder that has
another listview.builder
Don't want it to be scrolled alone it to be scrolled with the other
widgets in the home screen
Let's try this code, hopefully you will get your answerers and solution together:
SingleChildScrollView(
child: Column(
children: <Widget>[
Search(),
FeaturedProducts(),
OnSaleProducts(),
],
),
),
Now do this for OnSaleProducts() widget:
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height, // better for fixed size
width: double.infinity,
child: FutureBuilder<PopularFoodList>(
future: getOnSaleProduct(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 9,
itemBuilder: (context, index) {
var item = snapshot.data[index]; // snapshot.data.anotherProperty[index]; // If your model has anotherProperty
return InkWell(child: ProductCard(name: item.name, price: item.price, picture: '', onSale: true));
});
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
return Center(child: CircularProgressIndicator());
})
);
}

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

How to display progress indicator in flutter when a certain condition is true?

I have widget which return CircularProgressIndicator()
it shows on the circular mark upper left of screen.
However I want to put this as overlay and put at the center of screen.
I am checking widget list but I cant find what Widget should I use as overlay.
On which layer should I put this on??
For now my code is like this ,when loading it shows CircularProgressIndicator instead of ListView
However I want to put CircularProgressIndicator() on ListView
Widget build(BuildContext context) {
if(loading) {
return CircularProgressIndicator();
}
return ListView.builder(
controller: _controller,
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index]),
);
},
);
}
Thank you very much for answers.
I solve with stack Widget like this below.
At first I try to use overlay, but I bumped into some errors.
So, I use simply stack.
#override
Widget build(BuildContext context) {
Widget tempWidget = new CircularProgressIndicator();
if(loading) {
tempWidget = new CircularProgressIndicator();
}
else {
tempWidget = new Center();//EmptyWidget
}
return Stack(
children: <Widget>[
ListView.builder(
controller: _controller,
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
onTap: () => onTapped(context,articles[index].url),
);
},
),
Center(
child: tempWidget
),
]
);
}
Stack(
children: <Widget>[
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
color: Colors.red,
);
},
itemCount: 10,
),
Center(
child: CircularProgressIndicator(),
),
],
)
Stack(
children: <Widget>[
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
color: Colors.red,
);
},
itemCount: 10,
),
isLoading? Container(child: Center(
child: CircularProgressIndicator(),
)): Container(), //if isLoading flag is true it'll display the progress indicator
],
)
or you can use futureBuilder or streamBuilder when loading data from somewhere and you want to change the ui depending on the state
To overlay or position items on top of each other you would usually use a Stack widget or Overlay as described here. For your usecase I would recommend checking out the modal progress hud package.