I need to have the swipe effect of a vertical PageView and the effect the SliverAppBar.
If I set the scrollDirection: Axis.vertical of the PageView.builder and swipe vertical, when I swipe back I can't get the SliverAppBar.
Setting scrollDirection: Axis.horizontal the SliverAppBar works, but as mentioned I need it vertical.
I didn't found any good way to fix this yet, can you help me to understand?
My final objective is to have the first "screen" with buttons (that´s why I am using the SliverAppBar) and next screens will be a fullscreen image gallery. If you have a better solution that don´t use Slivers or PageView, please advise me.
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: MediaQuery.of(context).size.height*0.85,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'https://source.unsplash.com/random?monochromatic+dark',
fit: BoxFit.cover,
),
title: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Row(
children: [
ElevatedButton(
onPressed: () => print('btn1'),
child: Text('Button 1')),
ElevatedButton(
onPressed: () => print('btn2'),
child: Text('Button 2')),
],
),
),
),
),
];
},
body: _pageView(),
),
);
}
_pageView() {
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Container(
padding: EdgeInsets.all(16.0),
child: Image.network(
'https://source.unsplash.com/random?sig=$index',
fit: BoxFit.cover,
),
),
);
},
);
}
}
If you want to view the full example or test it on your side:
https://pastebin.com/PXDHVw6k
Add SliverOverlapAbsorber before SliverAppBar;
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverAppBar(
...
),
),
];
},
body: _pageView(),
),
use custom scroll wheel and using sliver.list instead of pageview and also add physics: PageScrollPhysics() to custom scroll wheel to get the same page snapping effect as pageview.
CustomScrollView(
physics: PageScrollPhysics(),
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ... ;
},
childCount: ...,
),
),
],
),
Related
After many attempts and trying different things, I get the same error ( throw constraintsError ) as soon as I add Expanded, while the error disappears by deleting it,, i I want the upper part to be fixed and the other part to be Scrollable
thanks
SingleChildScrollView(
child: Column(
children: [
Column(
children: [
Container(), // fixed
Row(), // fixed
**Expanded**(
child: Container(
color: constants.kLightGrayColor,
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
controller: controller.scrollController,
itemCount: data.posts.length,
itemBuilder: (context, index) {
firstly cannot used Expanded inside SingleChildScrollView.
to work normally pls remove SingleChildScrollView widget and remove outer Column,
used one of Column in green box.
it is small example
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Container(
height: 200,
width: MediaQuery.of(context).size.width,
color: Colors.red,
child: const Center(child: Text("Fixed Box")),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
},
),
),
],
),
);
}
In my application I am listing in an appBar several Containers that have the names of product categories, these categories are being listed in the body with their respective products.
The ListView that is in the appBar has the same indexes of the ListView of the body, so the idea was to press the index 'x' in the appBar and the user would be redirected to the index 'x' in the body.
I tried many solutions, one of then was the package https://pub.dev/packages/scrollable_positioned_list, but it did not works because when calling the function to scroll my list just disappears.
Here's de code:
return Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: Size.fromHeight(120),
child: Column(
children: [
AppBar(...),
Expanded(
child: Container(
color: AppColors.primary,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.listaProdutos.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.symmetric(...),
child: GestureDetector(
child: Container(
decoration: BoxDecoration(...),
child: Padding(...),
child: Center(
child: Text(
widget.listaProdutos[index].dsGrupo,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
),
onTap: () {
SHOULD SCROLL TO INDEX
},
),
);
},
)
),
),
],
),
),
body: SingleChildScrollView(
child: Column(
children: [
Container(
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.listaProdutos.length,
itemBuilder: (context, indexGrupo) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(...),
ListView.builder(..),
],
);
},
),
),
],
),
),
);
You can use PageView with scrollDirection: Axis.vertical,
class TFW extends StatefulWidget {
const TFW({super.key});
#override
State<TFW> createState() => _TFWState();
}
class _TFWState extends State<TFW> {
final PageController controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: PreferredSize(
preferredSize: Size.fromHeight(100),
child: Expanded(
child: ListView.builder(
itemCount: 100,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
controller.animateToPage(index,
duration: Duration(milliseconds: 400),
curve: Curves.easeIn);
},
child: SizedBox(width: 100, child: Text("$index")),
);
},
),
),
)),
body: PageView.builder(
controller: controller,
itemCount: 100,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Container(
color: index.isEven ? Colors.red : Colors.blue,
child: Text("$index"),
);
},
),
);
}
}
Fortunately I was able to resolve the problem. To help members who may have the same doubt I will register here the solution that worked for me. (sorry for the bad English)
Question: Why ScrollablePositionedList wasn't working? (as I mentioned iniatily)
Response: I was using the ScrollablePositionedList within a SingleChildScrollView, and for some reason when using the scrollTo or jumpTo function, the information that was visible simply disappeared. For that reason, I was trying to find a way to get success using a ListView (what came to nothing).
Solution: ... Trying to figure out why the ScrollablePositionedList wasn't working as it should ...
The initial structure was:
body: SingleChildScrollView(
child: Column(
children: [
Container(
child: ScrollablePositionedList.builder(
Changed for:
body: ScrollablePositionedList.builder(
The only reason for all this confusion is that ScrollablePositionedList's indexing functions for some reason don't work as they should if it's inside a SingleChildScrollView. So, take off SingleChildScrollView and all good.
My singleChildScollView won't scroll vertically. I've checked similar questions regarding this problem but none of them sem to work for me.
The page fills up with the items, and the homepage button is placed at the bottom...
...so it looks correct, it just doesn't let me scroll.
Can anyone help? [ EDIT - please note in the example code below I have simplified the original widget tree and removed the homepage button]...
Scaffold(
body: SingleChildScrollView(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (ctx, index) {
...list of items here...
}
),
),
)
There is two steps you can do to use SingleChildScrollView in a Column widget
Wrap it in a SizedBox
Set a height to the SizedBox widget
Try this out :
Scaffold(
body:
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(
//set a height
height : MediaQuery.of(context).size.height/5,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (ctx, index) {
...list of items here...
}
),
],
),
),
),
Center(
child: ElevatedButton(
onPressed: () {Navigator.pop(context);},
child: Text('Homepage'),
),
),
],
),
)
#james please check it
Scaffold(
body: Stack(
children: [
SingleChildScrollView(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: ScrollPhysics(),
itemCount: 30,
itemBuilder: (ctx, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text("index $index"),
);
}
),
),
Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
onPressed: () {Navigator.pop(context);},
child: Text('Homepage'),
),
),
],
),
)
thanks for the help, expecially #Jahidul Islam.
I was missing the physics: ScrollPhysics() line!
In order for the SingleChildScrollView to work, its parent's height should be defined.
You can achieve this by wrapping the SingleChildScrollView in a Container/SizedBox, or by wrapping it with the Expanded widget.
I'm new to Flutter and I like the PageView widget, but how do I always have an AppBar or other elements on top?
Now PageView is changing the entire page, along with the AppBar, how can you pin it? To make the pages scroll under the AppBar
Scaffold(
body: Stack(
children: [
Padding(
padding: EdgeInsets.only(top: 50),
child: Container(
width: double.infinity,
height: 100,
color: Colors.blue,
),
),
FutureBuilder(
future: _futureMenu,
builder: (context, snapshot){
if(snapshot.hasData){
return PageView.builder(
itemBuilder: (context, position) {
return PageForPosition();
},
itemCount: snapshot.data.length, // Can be null
);
} else if (snapshot.hasError){
}
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
),
],
),
)
Place the AppBar widget in scaffold appBar parameter.
Scaffold(
appBar: AppBar(),//<-- Move appbar here.
body: Stack(
children: [
FutureBuilder(
future: _futureMenu,
builder: (context, snapshot){
if(snapshot.hasData){
return PageView.builder(
itemBuilder: (context, position) {
return PageForPosition();
},
itemCount: snapshot.data.length, // Can be null
);
} else if (snapshot.hasError){
return Center(child:Text('Error'));
}
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
),
],
),
)
Check this dart pad.
You could try use sliverAppbar code like this
SliverAppBar(
expandedHeight: 300.0,
pinned: true,
Pinned allows it to stay fixed to the top if you need additional help on this way of creating an app bar let me know happy to help further explain
You can use CustomScrollView like below to gain your UI and effect.
CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('Grid Item $index'),
);
},
childCount: 20,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('List Item $index'),
);
},
),
),
],
)
I have a problem, when I want to scroll on the screen, the app doesn't scroll. What am I missing?
This is my
code
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * .7,
child: ListView.builder(
itemCount: itemsDishes.length,
itemBuilder: (context, index) {
return itemsDishes[index];
},
)),
Container(
height: MediaQuery.of(context).size.height * .9,
child: ListView.builder(
itemCount: itemsDrinks.length,
itemBuilder: (context, index) {
return itemsDrinks[index];
},
)),
],
),
),
),
);
As you can see, the column is nested within a SingleChildScrollView
Your inner ListView widgets are capturing the scroll event but don't contain enough items to scroll themselves, and when you're using a SingleChildScrollView an inner ListView is redundant anyway. I'd recommend changing them to Column:
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: itemsDishes,
),
Column(
mainAxisSize: MainAxisSize.min,
children: itemsDrinks,
),
],
),
),
),
);
Remove Containers wrapping your ListViews and set shrinkWrap as well as physics properties.
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemsDishes.length,
itemBuilder: (context, index) {
return itemsDishes[index];
},
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemsDrinks.length,
itemBuilder: (context, index) {
return itemsDrinks[index];
},
)
You may just not have enough items on your screen!
You can also set physics: const AlwaysScrollableScrollPhysics(), on your SingleChildScrollView.