PageView inside SliverChildBuilderDelegate starting at last page when scrolling fast - flutter

I needed a floating SliverAppBar with TabBarView. Each tab has a CustomScrollView to scroll it's Childs. If i put PageView as a child and i scroll fast then the PageView starts with last page instead of first page. I found a sample code here in flutter docs:
https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html
And i just changed the ListTile inside the SliverChildBuilderDelegate with a PageView. But when i test this code and scroll fast the pageView starts with last page.
Here's a demo of that
https://gyazo.com/79709286236fa3dbf1f88b1e92f5cee3
And here's my code:
// This example shows a [NestedScrollView] whose header is the combination of a
// [TabBar] in a [SliverAppBar] and whose body is a [TabBarView]. It uses a
// [SliverOverlapAbsorber]/[SliverOverlapInjector] pair to make the inner lists
// align correctly, and it uses [SafeArea] to avoid any horizontal disturbances
// (e.g. the "notch" on iOS when the phone is horizontal). In addition,
// [PageStorageKey]s are used to remember the scroll position of each tab's
// list.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final List<String> _tabs = <String>['Tab 1', 'Tab 2'];
return DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title:
const Text('Books'), // This is the title in the app bar.
pinned: true,
floating: true,
expandedHeight: 150.0,
// The "forceElevated" property causes the SliverAppBar to show
// a shadow. The "innerBoxIsScrolled" parameter is true when the
// inner scroll view is scrolled beyond its "zero" point, i.e.
// when it appears to be scrolled below the SliverAppBar.
// Without this, there are cases where the shadow would appear
// or not appear inappropriately, because the SliverAppBar is
// not actually aware of the precise position of the inner
// scroll views.
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return PageView(
/// [PageView.scrollDirection] defaults to [Axis.horizontal].
/// Use [Axis.vertical] to scroll vertically.
scrollDirection: Axis.horizontal,
children: const <Widget>[
Center(
child: Text('First Page'),
),
Center(
child: Text('Second Page'),
),
Center(
child: Text('Third Page'),
)
],
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 100,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
How can i make the pageView to starts with first page? I tried setting this to pageView controller: PageController(initialPage: 0), But it didn't work.

Related

How to add Grids into TabbarView in Flutter?

So basically I have this widget:
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// ...
SliverToBoxAdapter(
child: TabBar(
controller: this._controller,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 1.0, color: Colors.red),
),
tabs: [
Tab(icon: Icon(CupertinoIcons.camera)),
Tab(icon: Icon(CupertinoIcons.photo)),
Tab(icon: Icon(CupertinoIcons.video_camera)),
],
),
),
SliverFillRemaining(
child: TabBarView(
controller: this._controller,
children: [
// Want Scrollable Grid here
// Want Scrollable Grid here
Center(
child: Text("Hello Reader🙂"),
),
],
),
),
// ...
],
),
);
}
}
I want to add a 2 scrollable grids as children in the TabBarView however when I use GridView.builder(...), there is an annoying gap at the top of the grid and scrolling isn't all too great neither even with shrinkWrap: true and physics: NeverScrollableScrollPhysics().
However when I use a SliverGrid(...), there is this error
RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
This obviously makes sense because TabBarView isn't a sliver widget. I have already taken a look at this post but it wasn't really of any help.
How could I implement this? Is there perhaps a way I could create my own widget builder that builds a custom layout?
Thank You!
You need to use SliverOverlapAbsorber/SliverOverlapInjector, the following code works for me (working full code on dart pad):
Here i used SliverFixedExtentList but you can it replace with SliverGrid.
#override
State<StatefulWidget> createState() => _NewsScreenState();
}
class _NewsScreenState extends State<NewsScreen> {
final List<String> listItems = [];
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
primary: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs
.map((String name) => Tab(text: name))
.toList(),
),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is "inside"
// the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
// find the NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 60.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return Container(
color: Color((math.Random().nextDouble() *
0xFFFFFF)
.toInt() <<
0)
.withOpacity(1.0));
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
),
);
}
}

How to constraints width and height of `Scaffold` (useful for Web and Desktop)

The screenshot below is a Scaffold on Desktop (should be similar on browser):
Is it possible to constraints Scaffold to a min size (width and height)?
If screen width is smaller than the min width, a horizontal scrollbar appears to move left and right.
If screen height is smaller than the min height, a vertical scrollbar appears to move up and down.
Ex. designers in our project want a min width of 960px on browser.
Here's an example main.dart that uses LayoutBuilder to constrain the scaffold (both the body and the appBar) to a min of 480dp, and if the width constraints are less than that, wraps the scaffold inside a horizontal ScrollView with a ScrollBar. And if the constraint height is less than 480dp, it wraps scaffold (which may already be wrapped or not) in a vertical scroll.
If both width and height are less than 480dp, 2 scrollbars are visible. In this case, the widget tree must be ScrollBar > ScrollBar > ScrollView > ScrollView. If widget tree is ScrollBar > ScrollView > ScrollBar > ScrollView, the nested Scrollbar is only visible when the parent ScrollBar is scrolled to the edge side.
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
// set default isAlwaysShown, so don't need to set for individual Scrollbar.
theme: ThemeData(scrollbarTheme: ScrollbarThemeData(isAlwaysShown: true)),
home: App(),
));
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
final minWidth = 480.0;
final minHeight = 480.0;
ScrollController _horizontalController = ScrollController();
ScrollController _verticalController = ScrollController();
#override
void dispose() {
_horizontalController.dispose();
_verticalController.dispose();
super.dispose();
}
Widget _buildScaffold() {
return Scaffold(
appBar: AppBar(title: Text('2D Scrollbars')),
body: Container(color: Colors.amber),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
],
),
);
}
#override
Widget build(BuildContext context) {
final scaffold = _buildScaffold();
return LayoutBuilder(
builder: (context, constraints) {
final horizontalScrollbarEnabled = constraints.minWidth < minWidth;
final verticalScrollbarEnabled = constraints.minHeight < minHeight;
if (horizontalScrollbarEnabled && verticalScrollbarEnabled) {
return Scrollbar(
controller: _horizontalController,
child: Scrollbar(
// IMPORTANT: this scrollbar only handle notification of the vertical ScrollView.
// The first ScrollView (depth = 0), is the horizontal one.
// The second ScrollView (depth = 1), is the vertical one.
// If notification.depth != 1, the notification is bubble up to horizontal Scrollbar.
notificationPredicate: (notification) => notification.depth == 1,
controller: _verticalController,
child: SingleChildScrollView(
controller: _horizontalController,
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
controller: _verticalController,
child: Container(
width: minWidth,
height: minHeight,
child: scaffold,
),
),
),
),
);
} else if (horizontalScrollbarEnabled) {
return Scrollbar(
controller: _horizontalController,
child: SingleChildScrollView(
controller: _horizontalController,
scrollDirection: Axis.horizontal,
child: Container(
width: minWidth,
child: scaffold,
),
),
);
} else if (verticalScrollbarEnabled) {
return Scrollbar(
controller: _verticalController,
child: SingleChildScrollView(
controller: _verticalController,
child: Container(
height: minHeight,
child: scaffold,
),
),
);
}
return scaffold;
},
);
}
}

NestedScrollView body keeps scrolling even if empty space is available

My requirement is to create a page with collapsing toolbar and two tabs.
To do this, I am using the code below.
SafeArea(
child: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverPersistentHeader(
pinned: true,
delegate: _Header(),
)
];
},
body: TabBarView(
children: <Widget>[
Container1(),
Container2(),
],
),
),
),
)
Everything works, but once the scrolling starts and the header collapses, the body keeps on scrolling even if all the widgets are visible.
How do I make it behave like Android native, where if the list is small in size, the list doesn't scroll after the header collapses.
For anyone who is facing the same issue, this is how I fixed it.
You can just copy the entire code and run it to check and modify accordingly.
The code is taken from the official documentation.
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_module/app_constants.dart';
import 'package:flutter_module/data/local/shared_pref.dart';
import 'package:flutter_module/data/remote/api_end_points.dart';
import 'package:flutter_module/string_localization.dart';
import 'package:flutter_module/ui/router.dart';
import 'package:flutter_module/util.dart';
import 'package:http/http.dart' as http;
import 'data/local/dao/sync_dao.dart';
import 'data/remote/response_pojo/video_challenges_response.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainCollapsingToolbar(),
);
}
}
class MainCollapsingToolbar extends StatefulWidget {
#override
_MainCollapsingToolbarState createState() => _MainCollapsingToolbarState();
}
class _MainCollapsingToolbarState extends State<MainCollapsingToolbar> {
#override
Widget build(BuildContext context) {
var _tabs = ["One", "Two"];
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(minHeight: 100, maxHeight: 150, child: Container(
color: Colors.blue,
)),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return ListTile(
title: Text('Item $index'),
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 1,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
wrap your body with Expanded widget

call back listview inside another listview with another widget?

I'm trying to call list view inside the body with a list view its shows nothing "empty"
How I can fix that by calling another list view inside list view? It's hard to explain that.
body: new ListView(
children: <Widget>[
HomePageListView(),
],
),
The following is Nested ListView example. please notice shrinkWrap: true and Container height
You can refactor inner part to another widget
code snippet original
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return ExpansionTile(
title: Text(litems[Index]),
children: <Widget>[
Text(litems[Index]),
Container(
height : 300,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: litems1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems1[Index]);
}),
)
],
);
}),
)
code snippet refactor to widget
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return ExpansionTile(
title: Text(litems[Index]),
children: <Widget>[
Text(litems[Index]),
new innerOneWidget(litems1: litems1)
],
);
}),
),
...
class innerOneWidget extends StatelessWidget {
const innerOneWidget({
Key key,
#required this.litems1,
}) : super(key: key);
final List<String> litems1;
#override
Widget build(BuildContext context) {
return Container(
height : 300,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: litems1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems1[Index]);
}),
);
}
}
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
List<String> litems = ['1', '2', '3', '4'];
List<String> litems1 = [' hi ', ' this ', ' is ', ' long ', ' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long '];
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return ExpansionTile(
title: Text(litems[Index]),
children: <Widget>[
Text(litems[Index]),
new innerOneWidget(litems1: litems1)
],
);
}),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class innerOneWidget extends StatelessWidget {
const innerOneWidget({
Key key,
#required this.litems1,
}) : super(key: key);
final List<String> litems1;
#override
Widget build(BuildContext context) {
return Container(
height : 300,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: litems1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems1[Index]);
}),
);
}
}
demo picture, outer ListView scroll vertical and inner ListView scroll horizontal
just true shrinkWrap
shrinkWrap : true
On your HomePageListView set
shrinkWrap : true
to listview
Use bellow properties on the second List
shrinkWrap: true,
physics: NeverScrollableScrollPhysics()

Nested scrollbars issue: NestedScrollView, SliverAppBar, SliverPersistentHeader, TabBarView, and PageView

This issue has been haunting me for some time and even though I read countless web pages, I still cannot solve it. Maybe you're able to help out!
I got the following scenario:
A Flutter app has a PageView with just 3 pages.
First page has a simple GridView (vertical scrolling) and no problem there with nested scrolling.
Third page is a simple ListView with lists of items, no problem here also with scrolling.
The second page has a NestedScrollView, with two widgets for its headerSliverBuilder: SliverAppBar and SliverPersistentHeader. Nothing fancy here.
The body of the NestedScrollView contains a TabBarView which contains 3 tabs, and it's possible to swipe between them using a horizontal swipe -- the same swipe direction as the PageView which contains this page.
The body is where the scrolling problem occurs.
Swiping between the 3 tabs works like a charm. However, when the current tab is the first and you try to swipe further to the left (finger motion is from left-to-right), the first page (belonging to PageView) doesn't show. Conversely, when the current tab is the 3rd and you try to swipe further to the right (finger motion is from right-to-left), nothing happens.
If you do the same finger motion on the header contained in the second page, the pages turn fine (either to the first or third pages).
Here's the code inside the second page, would love to know why swiping motion inside the TabBarView isn't propagating to the container when tabs reach the edges:
Scaffold(
appBar: _generateAppBar(),
body: DefaultTabController(
length: 3,
child: NestedScrollView(
headerSliverBuilder: (_, __) => [
SliverAppBar(
backgroundColor: backgroundColor,
elevation: 0.0,
expandedHeight: 200.0,
floating: true,
pinned: false,
flexibleSpace: backgroundImageView,
),
SliverPersistentHeader(
floating: false,
delegate: _SliverAppBarDelegate(
TabBar(
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Colors.black26,
indicatorWeight: 2.5,
tabs: const [
Text('Tab 1'),
Text('Tab 2'),
Text('Tab 3'),
],
),
),
pinned: true,
),
],
body: TabBarView(
children: [
Center(child: Text('Body 1')),
Center(child: Text('Body 2')),
Center(child: Text('Body 3')),
],
),
),
),
);
Auxiliary class:
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) =>
Container(child: _tabBar);
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) =>
false;
}
Any idea how to fix this? Thanks!
You should wrap the
NestedScrollView like
DefaultTabController(
length: tabs.length,
child: NestedScrollView())