I have a SingleChildScrollView and it contains a scrollable TabBar, and I have a NotificationListener which listens for the notifications.
My problem is I get ScrollNotification from both widgets and I do not know how to tell which of the two fired the notification.
ScrollNotification contains BuildContext? and the comment above suggests that this can be used to determine the source, but I do not understand how to use the build context to work out which of my objects fired the notification.
/// The build context of the widget that fired this notification.
///
/// This can be used to find the scrollable's render objects to determine the
/// size of the viewport, for instance.
final BuildContext? context;
CodePudding user response:
I found a way that works but is a little cumbersome. However, it might help you if you do not find an easier solution:
Create your own Notification
by extending ScrollNotification
. This allows you to add some field like an id, which you can use in your NotificationListener
to distinguish where the Notification
originates from.
But the cumbersome part is, say you have two widgets that dispatch notifications, you will have to wrap both with their own NotificationListener
and use its onNotification
method to catch the Notifications and dispatch your custom Notification
, with an added id field for recognition, instead.
Below is a complete code sample, in which I have used the above-mentioned solution, dealing with notifications from two different ListView
s:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> items = List.filled(20, "SomeText");
@override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener<MyNotification>(
onNotification: (notif) {
print(notif.id);
return true;
},
child: LayoutBuilder(
builder: (context, constraints) {
return Column(
children: [
SizedBox(
height: constraints.maxHeight / 2,
width: constraints.maxWidth,
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
MyNotification(
id: "First ListView",
metrics: notification.metrics,
context: notification.context)
.dispatch(context);
return true;
},
child: ListView.builder(
controller: ScrollController(),
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
items[index],
),
);
},
),
),
),
SizedBox(
height: constraints.maxHeight / 2,
width: constraints.maxWidth,
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
MyNotification(
id: "SecondListView",
metrics: notification.metrics,
context: notification.context)
.dispatch(context);
return true;
},
child: ListView.builder(
controller: ScrollController(),
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
items[index],
),
);
},
),
),
),
],
);
},
),
),
);
}
}
class MyNotification extends ScrollNotification {
MyNotification(
{required this.id,
required ScrollMetrics metrics,
required BuildContext? context})
: super(metrics: metrics, context: context);
final String id;
}
CodePudding user response:
Yes, the BuildContext
can be used for that. You can use findAncestorWidgetOfExactType
to find whether the triggering widget has a certain ancestor widget type.
For example, I have a screen with 3 scrollable things: a ListView
, a GridView
and a horizontally scrolling SingleChildScrollView
, as shown below:
In the NotificationListener
, we can then check the source of the event, like so:
// Cast the `BuildContext` into an `Element`
final element = (notification.context as Element);
// Try to see if the `Element` has an ancestor of type `ListView`, `GridView` or `SingleChildScrollView`.
final listView = element.findAncestorWidgetOfExactType<ListView>();
final gridView = element.findAncestorWidgetOfExactType<GridView>();
final scrollView = element.findAncestorWidgetOfExactType<SingleChildScrollView>();
// One of them should have a non-null value, the other two would be null.
print('listView: $listView');
print('gridView: $gridView');
print('scrollView: $scrollView');
Full example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(backgroundColor: Colors.black),
body: NotificationListener(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
final element = (notification.context as Element);
final listView = element.findAncestorWidgetOfExactType<ListView>();
final gridView = element.findAncestorWidgetOfExactType<GridView>();
final scrollView =
element.findAncestorWidgetOfExactType<SingleChildScrollView>();
print('listView: $listView');
print('gridView: $gridView');
print('scrollView: $scrollView');
}
return false;
},
child: Column(
children: [
Expanded(
child: ListView.builder(
itemBuilder: (context, index) => ListTile(
title: Text('List View Item $index'),
),
),
),
Expanded(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (context, index) => Center(
child: Text('Grid View Item $index'),
),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < 100; i ) FlutterLogo(),
],
),
),
),
],
),
),
);
}
}