// 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. import 'package:flutter/material.dart'; /// Flutter code sample for [ScrollNotificationObserver]. void main() => runApp(const ScrollNotificationObserverApp()); class ScrollNotificationObserverApp extends StatelessWidget { const ScrollNotificationObserverApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(useMaterial3: true), // The Scaffold widget contains a [ScrollNotificationObserver]. // This is used by [AppBar] for its scrolled under behavior. // // We can use [ScrollNotificationObserver.maybeOf] to get the // state of this [ScrollNotificationObserver] from descendants // of the Scaffold widget. // // If you're not using a [Scaffold] widget, you can create a [ScrollNotificationObserver] // to notify its descendants of scroll notifications by adding it to the subtree. home: Scaffold( appBar: AppBar( title: const Text('ScrollNotificationObserver Sample'), ), body: const ScrollNotificationObserverExample(), ), ); } } class ScrollNotificationObserverExample extends StatefulWidget { const ScrollNotificationObserverExample({super.key}); @override State<ScrollNotificationObserverExample> createState() => _ScrollNotificationObserverExampleState(); } class _ScrollNotificationObserverExampleState extends State<ScrollNotificationObserverExample> { ScrollNotificationObserverState? _scrollNotificationObserver; ScrollController controller = ScrollController(); bool _scrolledDown = false; @override void didChangeDependencies() { super.didChangeDependencies(); // Remove any previous listener. _scrollNotificationObserver?.removeListener(_handleScrollNotification); // Get the ScrollNotificationObserverState from the Scaffold widget. _scrollNotificationObserver = ScrollNotificationObserver.maybeOf(context); // Add a new listener. _scrollNotificationObserver?.addListener(_handleScrollNotification); } @override void dispose() { if (_scrollNotificationObserver != null) { _scrollNotificationObserver!.removeListener(_handleScrollNotification); _scrollNotificationObserver = null; } controller.dispose(); super.dispose(); } void _handleScrollNotification(ScrollNotification notification) { // Check if the notification is a scroll update notification and if the // `notification.depth` is 0. This way we only listen to the scroll // notifications from the closest scrollable, instead of those that may be nested. if (notification is ScrollUpdateNotification && defaultScrollNotificationPredicate(notification)) { final ScrollMetrics metrics = notification.metrics; // Check if the user scrolled down. if (_scrolledDown != metrics.extentBefore > 0) { setState(() { _scrolledDown = metrics.extentBefore > 0; }); } } } @override Widget build(BuildContext context) { return Stack( children: <Widget>[ SampleList(controller: controller), // Show the button only if the user scrolled down. if (_scrolledDown) Positioned( right: 25, bottom: 20, child: Center( child: GestureDetector( onTap: () { // Scroll to the top when the user taps the button. controller.animateTo(0, duration: const Duration(milliseconds: 200), curve:Curves.fastOutSlowIn); }, child: const Card( child: Padding( padding: EdgeInsets.all(8.0), child: Column( children: <Widget>[ Icon(Icons.arrow_upward_rounded), Text('Scroll to top') ], ), ), ), ), ), ), ], ); } } class SampleList extends StatelessWidget { const SampleList({super.key, required this.controller}); final ScrollController controller; @override Widget build(BuildContext context) { return ListView.builder( controller: controller, itemCount: 30, itemBuilder: (BuildContext context, int index) { return ListTile(title: Text('Item $index')); }, ); } }