blob: 78459bc59c1e61272264270d8ad7460773d6f59f [file] [log] [blame]
// 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'));
},
);
}
}