1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:flutter/material.dart'; |
6 | |
7 | /// Flutter code sample for [NestedScrollView]. |
8 | |
9 | void main() => runApp(const NestedScrollViewExampleApp()); |
10 | |
11 | class NestedScrollViewExampleApp extends StatelessWidget { |
12 | const NestedScrollViewExampleApp({super.key}); |
13 | |
14 | @override |
15 | Widget build(BuildContext context) { |
16 | return const MaterialApp( |
17 | home: NestedScrollViewExample(), |
18 | ); |
19 | } |
20 | } |
21 | |
22 | class NestedScrollViewExample extends StatelessWidget { |
23 | const NestedScrollViewExample({super.key}); |
24 | |
25 | @override |
26 | Widget build(BuildContext context) { |
27 | final List<String> tabs = <String>['Tab 1' , 'Tab 2' ]; |
28 | return DefaultTabController( |
29 | length: tabs.length, // This is the number of tabs. |
30 | child: Scaffold( |
31 | body: NestedScrollView( |
32 | headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { |
33 | // These are the slivers that show up in the "outer" scroll view. |
34 | return <Widget>[ |
35 | SliverOverlapAbsorber( |
36 | // This widget takes the overlapping behavior of the SliverAppBar, |
37 | // and redirects it to the SliverOverlapInjector below. If it is |
38 | // missing, then it is possible for the nested "inner" scroll view |
39 | // below to end up under the SliverAppBar even when the inner |
40 | // scroll view thinks it has not been scrolled. |
41 | // This is not necessary if the "headerSliverBuilder" only builds |
42 | // widgets that do not overlap the next sliver. |
43 | handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), |
44 | sliver: SliverAppBar( |
45 | title: const Text('Books' ), // This is the title in the app bar. |
46 | pinned: true, |
47 | expandedHeight: 150.0, |
48 | // The "forceElevated" property causes the SliverAppBar to show |
49 | // a shadow. The "innerBoxIsScrolled" parameter is true when the |
50 | // inner scroll view is scrolled beyond its "zero" point, i.e. |
51 | // when it appears to be scrolled below the SliverAppBar. |
52 | // Without this, there are cases where the shadow would appear |
53 | // or not appear inappropriately, because the SliverAppBar is |
54 | // not actually aware of the precise position of the inner |
55 | // scroll views. |
56 | forceElevated: innerBoxIsScrolled, |
57 | bottom: TabBar( |
58 | // These are the widgets to put in each tab in the tab bar. |
59 | tabs: tabs.map((String name) => Tab(text: name)).toList(), |
60 | ), |
61 | ), |
62 | ), |
63 | ]; |
64 | }, |
65 | body: TabBarView( |
66 | // These are the contents of the tab views, below the tabs. |
67 | children: tabs.map((String name) { |
68 | return SafeArea( |
69 | top: false, |
70 | bottom: false, |
71 | child: Builder( |
72 | // This Builder is needed to provide a BuildContext that is |
73 | // "inside" the NestedScrollView, so that |
74 | // sliverOverlapAbsorberHandleFor() can find the |
75 | // NestedScrollView. |
76 | builder: (BuildContext context) { |
77 | return CustomScrollView( |
78 | // The "controller" and "primary" members should be left |
79 | // unset, so that the NestedScrollView can control this |
80 | // inner scroll view. |
81 | // If the "controller" property is set, then this scroll |
82 | // view will not be associated with the NestedScrollView. |
83 | // The PageStorageKey should be unique to this ScrollView; |
84 | // it allows the list to remember its scroll position when |
85 | // the tab view is not on the screen. |
86 | key: PageStorageKey<String>(name), |
87 | slivers: <Widget>[ |
88 | SliverOverlapInjector( |
89 | // This is the flip side of the SliverOverlapAbsorber |
90 | // above. |
91 | handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), |
92 | ), |
93 | SliverPadding( |
94 | padding: const EdgeInsets.all(8.0), |
95 | // In this example, the inner scroll view has |
96 | // fixed-height list items, hence the use of |
97 | // SliverFixedExtentList. However, one could use any |
98 | // sliver widget here, e.g. SliverList or SliverGrid. |
99 | sliver: SliverFixedExtentList( |
100 | // The items in this example are fixed to 48 pixels |
101 | // high. This matches the Material Design spec for |
102 | // ListTile widgets. |
103 | itemExtent: 48.0, |
104 | delegate: SliverChildBuilderDelegate( |
105 | (BuildContext context, int index) { |
106 | // This builder is called for each child. |
107 | // In this example, we just number each list item. |
108 | return ListTile( |
109 | title: Text('Item $index' ), |
110 | ); |
111 | }, |
112 | // The childCount of the SliverChildBuilderDelegate |
113 | // specifies how many children this inner list |
114 | // has. In this example, each tab has a list of |
115 | // exactly 30 items, but this is arbitrary. |
116 | childCount: 30, |
117 | ), |
118 | ), |
119 | ), |
120 | ], |
121 | ); |
122 | }, |
123 | ), |
124 | ); |
125 | }).toList(), |
126 | ), |
127 | ), |
128 | ), |
129 | ); |
130 | } |
131 | } |
132 | |