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 [ScrollController] & [ScrollNotification]. |
8 | |
9 | void main() => runApp(const ScrollNotificationDemo()); |
10 | |
11 | class ScrollNotificationDemo extends StatefulWidget { |
12 | const ScrollNotificationDemo({super.key}); |
13 | |
14 | @override |
15 | State<ScrollNotificationDemo> createState() => _ScrollNotificationDemoState(); |
16 | } |
17 | |
18 | class _ScrollNotificationDemoState extends State<ScrollNotificationDemo> { |
19 | ScrollNotification? _lastNotification; |
20 | late final ScrollController _controller; |
21 | bool _useController = true; |
22 | |
23 | // This method handles the notification from the ScrollController. |
24 | void _handleControllerNotification() { |
25 | print('Notified through the scroll controller.' ); |
26 | // Access the position directly through the controller for details on the |
27 | // scroll position. |
28 | } |
29 | |
30 | // This method handles the notification from the NotificationListener. |
31 | bool _handleScrollNotification(ScrollNotification notification) { |
32 | print('Notified through scroll notification.' ); |
33 | // The position can still be accessed through the scroll controller, but |
34 | // the notification object provides more details about the activity that is |
35 | // occurring. |
36 | if (_lastNotification.runtimeType != notification.runtimeType) { |
37 | setState(() { |
38 | // Call set state to respond to a change in the scroll notification. |
39 | _lastNotification = notification; |
40 | }); |
41 | } |
42 | |
43 | // Returning false allows the notification to continue bubbling up to |
44 | // ancestor listeners. If we wanted the notification to stop bubbling, |
45 | // return true. |
46 | return false; |
47 | } |
48 | |
49 | @override |
50 | void initState() { |
51 | _controller = ScrollController(); |
52 | if (_useController) { |
53 | // When listening to scrolling via the ScrollController, call |
54 | // `addListener` on the controller. |
55 | _controller.addListener(_handleControllerNotification); |
56 | } |
57 | super.initState(); |
58 | } |
59 | |
60 | @override |
61 | Widget build(BuildContext context) { |
62 | // ListView.separated works very similarly to this example with |
63 | // CustomScrollView & SliverList. |
64 | Widget body = CustomScrollView( |
65 | // Provide the scroll controller to the scroll view. |
66 | controller: _controller, |
67 | slivers: <Widget>[ |
68 | SliverList.separated( |
69 | itemCount: 50, |
70 | itemBuilder: (_, int index) { |
71 | return Padding( |
72 | padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 20.0), |
73 | child: Text('Item $index' ), |
74 | ); |
75 | }, |
76 | separatorBuilder: (_, _) => const Divider(indent: 20, endIndent: 20, thickness: 2), |
77 | ), |
78 | ], |
79 | ); |
80 | |
81 | if (!_useController) { |
82 | // If we are not using a ScrollController to listen to scrolling, |
83 | // let's use a NotificationListener. Similar, but with a different |
84 | // handler that provides information on what scrolling is occurring. |
85 | body = NotificationListener<ScrollNotification>( |
86 | onNotification: _handleScrollNotification, |
87 | child: body, |
88 | ); |
89 | } |
90 | |
91 | return MaterialApp( |
92 | theme: ThemeData.from(colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueGrey)), |
93 | home: Scaffold( |
94 | appBar: AppBar( |
95 | title: const Text('Listening to a ScrollPosition' ), |
96 | bottom: PreferredSize( |
97 | preferredSize: const Size.fromHeight(70), |
98 | child: Column( |
99 | mainAxisAlignment: MainAxisAlignment.spaceAround, |
100 | children: <Widget>[ |
101 | if (!_useController) Text('Last notification: ${_lastNotification.runtimeType}' ), |
102 | if (!_useController) const SizedBox.square(dimension: 10), |
103 | Row( |
104 | mainAxisAlignment: MainAxisAlignment.center, |
105 | children: <Widget>[ |
106 | const Text('with:' ), |
107 | Radio<bool>( |
108 | value: true, |
109 | groupValue: _useController, |
110 | onChanged: _handleRadioChange, |
111 | ), |
112 | const Text('ScrollController' ), |
113 | Radio<bool>( |
114 | value: false, |
115 | groupValue: _useController, |
116 | onChanged: _handleRadioChange, |
117 | ), |
118 | const Text('NotificationListener' ), |
119 | ], |
120 | ), |
121 | ], |
122 | ), |
123 | ), |
124 | ), |
125 | body: body, |
126 | ), |
127 | ); |
128 | } |
129 | |
130 | void _handleRadioChange(bool? value) { |
131 | if (value == null) { |
132 | return; |
133 | } |
134 | if (value != _useController) { |
135 | setState(() { |
136 | // Respond to a change in selected radio button, and add/remove the |
137 | // listener to the scroll controller. |
138 | _useController = value; |
139 | if (_useController) { |
140 | _controller.addListener(_handleControllerNotification); |
141 | } else { |
142 | _controller.removeListener(_handleControllerNotification); |
143 | } |
144 | }); |
145 | } |
146 | } |
147 | |
148 | @override |
149 | void dispose() { |
150 | _controller.removeListener(_handleControllerNotification); |
151 | super.dispose(); |
152 | } |
153 | } |
154 | |