// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flutter code sample for NestedScrollView import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); static const String _title = 'Flutter Code Sample'; @override Widget build(BuildContext context) { return const MaterialApp( title: _title, home: MyStatelessWidget(), ); } } class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({super.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, 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 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: 30, ), ), ), ], ); }, ), ); }).toList(), ), ), ), ); } }