NestedScrollView class
A scrolling view inside of which can be nested other scrolling views, with their scroll positions being intrinsically linked.
The most common use case for this widget is a scrollable view with a flexible SliverAppBar containing a TabBar in the header (build by headerSliverBuilder, and with a TabBarView in the body, such that the scrollable view's contents vary based on which tab is visible.
Motivation
In a normal ScrollView, there is one set of slivers (the components of the scrolling view). If one of those slivers hosted a TabBarView which scrolls in the opposite direction (e.g. allowing the user to swipe horizontally between the pages represented by the tabs, while the list scrolls vertically), then any list inside that TabBarView would not interact with the outer ScrollView. For example, flinging the inner list to scroll to the top would not cause a collapsed SliverAppBar in the outer ScrollView to expand.
NestedScrollView solves this problem by providing custom ScrollControllers for the outer ScrollView and the inner ScrollViews (those inside the TabBarView, hooking them together so that they appear, to the user, as one coherent scroll view.
In the example below, _tabs
is a list of strings, one for each tab, giving
the tab labels. In a real application, it would be replaced by the actual
data model being represented.
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: 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(),
),
),
)
- Inheritance
- Object
- Diagnosticable
- DiagnosticableTree
- Widget
- StatefulWidget
- NestedScrollView
Constructors
- NestedScrollView({Key key, ScrollController controller, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollPhysics physics, @required NestedScrollViewHeaderSliversBuilder headerSliverBuilder, @required Widget body })
-
Creates a nested scroll view. [...]
const
Properties
- body → Widget
-
The widget to show inside the NestedScrollView. [...]
final
- controller → ScrollController
-
An object that can be used to control the position to which the outer
scroll view is scrolled.
final
- headerSliverBuilder → NestedScrollViewHeaderSliversBuilder
-
A builder for any widgets that are to precede the inner scroll views (as
given by body). [...]
final
- physics → ScrollPhysics
-
How the scroll view should respond to user input. [...]
final
- reverse → bool
-
Whether the scroll view scrolls in the reading direction. [...]
final
- scrollDirection → Axis
-
The axis along which the scroll view scrolls. [...]
final
- hashCode → int
-
The hash code for this object. [...]
read-only, inherited
- key → Key
-
Controls how one widget replaces another widget in the tree. [...]
final, inherited
- runtimeType → Type
-
A representation of the runtime type of the object.
read-only, inherited
Methods
-
createState(
) → _NestedScrollViewState -
Creates the mutable state for this widget at a given location in the tree. [...]
override
-
createElement(
) → StatefulElement -
Creates a StatefulElement to manage this widget's location in the tree. [...]
inherited
-
debugDescribeChildren(
) → List< DiagnosticsNode> -
Returns a list of DiagnosticsNode objects describing this node's
children. [...]
@protected, inherited
-
debugFillProperties(
DiagnosticPropertiesBuilder properties) → void -
Add additional properties associated with the node. [...]
inherited
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a non-existent method or property is accessed. [...]
inherited
-
toDiagnosticsNode(
{String name, DiagnosticsTreeStyle style }) → DiagnosticsNode -
Returns a debug representation of the object that is used by debugging
tools and by toStringDeep. [...]
inherited
-
toString(
{DiagnosticLevel minLevel: DiagnosticLevel.debug }) → String -
Returns a string representation of this object.
inherited
-
toStringDeep(
{String prefixLineOne: '', String prefixOtherLines, DiagnosticLevel minLevel: DiagnosticLevel.debug }) → String -
Returns a string representation of this node and its descendants. [...]
inherited
-
toStringShallow(
{String joiner: ', ', DiagnosticLevel minLevel: DiagnosticLevel.debug }) → String -
Returns a one-line detailed description of the object. [...]
inherited
-
toStringShort(
) → String -
A short, textual description of this widget.
inherited
Operators
-
operator ==(
dynamic other) → bool -
The equality operator. [...]
inherited
Static Methods
-
sliverOverlapAbsorberHandleFor(
BuildContext context) → SliverOverlapAbsorberHandle - Returns the SliverOverlapAbsorberHandle of the nearest ancestor NestedScrollView. [...]