Add ScrollNotificationObserver
sample (#127023)
fixes https://github.com/flutter/flutter/issues/126702 ### Preview https://github.com/flutter/flutter/assets/48603081/4c529a0d-b8a5-4950-9095-429f1c5eccbb
This commit is contained in:
parent
3e610f7333
commit
c05a05e6fc
@ -0,0 +1,135 @@
|
|||||||
|
// 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'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
// 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';
|
||||||
|
import 'package:flutter_api_samples/widgets/scroll_notification_observer/scroll_notification_observer.0.dart' as example;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Scroll to top buttons appears when scrolling down', (WidgetTester tester) async {
|
||||||
|
const String buttonText = 'Scroll to top';
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const example.ScrollNotificationObserverApp(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byType(ScrollNotificationObserver), findsOneWidget);
|
||||||
|
expect(find.text(buttonText), findsNothing);
|
||||||
|
|
||||||
|
// Scroll down.
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(0.0, -300.0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text(buttonText), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text(buttonText));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text(buttonText), findsNothing);
|
||||||
|
});
|
||||||
|
}
|
@ -71,6 +71,15 @@ final class _ListenerEntry extends LinkedListEntry<_ListenerEntry> {
|
|||||||
/// This widget is similar to [NotificationListener]. It supports a listener
|
/// This widget is similar to [NotificationListener]. It supports a listener
|
||||||
/// list instead of just a single listener and its listeners run
|
/// list instead of just a single listener and its listeners run
|
||||||
/// unconditionally, they do not require a gating boolean return value.
|
/// unconditionally, they do not require a gating boolean return value.
|
||||||
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// This sample shows a "Scroll to top" button that uses [ScrollNotificationObserver]
|
||||||
|
/// to listen for scroll notifications from [ListView]. The button is only visible
|
||||||
|
/// when the user has scrolled down. When pressed, the button animates the scroll
|
||||||
|
/// position of the [ListView] back to the top.
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/widgets/scroll_notification_observer/scroll_notification_observer.0.dart **
|
||||||
|
/// {@end-tool}
|
||||||
class ScrollNotificationObserver extends StatefulWidget {
|
class ScrollNotificationObserver extends StatefulWidget {
|
||||||
/// Create a [ScrollNotificationObserver].
|
/// Create a [ScrollNotificationObserver].
|
||||||
///
|
///
|
||||||
|
Loading…
x
Reference in New Issue
Block a user