NotificationListener 详细使用指南
📌 什么是 NotificationListener? NotificationListener 是 Flutter 中用于监听各种系统通知事件的组件。 当列表滚动、键盘显示、尺寸变化等事件发生时,系统会向上传播相应的 Notification。
- ✨ 主要特点:
- 可以捕获和拦截各种 Notification 事件
- 通过 onNotification 回调处理事件
- 返回 true 表示消费事件(不再向上传播),返回 false 表示不消费
- depth 属性用于标识事件的深度(0 为最外层)
- 📚 常见的 Notification 类型:
- ScrollNotification - 滚动相关事件
- ScrollStartNotification: 滚动开始
- ScrollUpdateNotification: 滚动过程中(频繁触发)
- ScrollEndNotification: 滚动结束
- OverscrollNotification: 过度滚动
- SizeChangedLayoutNotification - 尺寸变化事件
- KeepAliveNotification - 保持状态事件
arduino
import 'package:flutter/material.dart';
【示例 1】监听滚动事件 - AppBar 渐显效果(项目中已使用)
scala
/// 通过监听滚动距离,实现 AppBar 透明度渐变效果
class ScrollNotificationDemo extends StatefulWidget {
const ScrollNotificationDemo({Key? key}) : super(key: key);
@override
State<ScrollNotificationDemo> createState() => _ScrollNotificationDemoState();
}
class _ScrollNotificationDemoState extends State<ScrollNotificationDemo> {
double _scrollOffset = 0; // 当前滚动距离(像素)
double _appBarAlpha = 0; // AppBar 透明度(0-1)
static const _appbarScrollOffset = 100; // 100像素后 AppBar 完全显示
/// 处理滚动事件
void _onScroll(double offset) {
// 计算透明度:0 - 100像素的范围内逐渐从 0 变化到 1
double alpha = offset / _appbarScrollOffset;
alpha = alpha.clamp(0.0, 1.0); // 限制在 0-1 之间
setState(() {
_scrollOffset = offset;
_appBarAlpha = alpha;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('滚动渐显效果'),
// AppBar 颜色根据滚动距离变化:从透明到蓝色
backgroundColor: Color.fromARGB(
(_appBarAlpha * 255).toInt(), // 透明度通道
33,
150,
243, // 蓝色
),
elevation: _appBarAlpha > 0.2 ? 4 : 0, // 滚动时显示阴影
),
body: NotificationListener<ScrollNotification>(
// 📌 指定泛型类型 <ScrollNotification> 可以让回调更类型安全
onNotification: (ScrollNotification notification) {
// 处理不同类型的滚动事件
if (notification is ScrollUpdateNotification) {
// ScrollUpdateNotification 在滚动过程中频繁触发
// depth == 0 表示这是最外层列表的事件
if (notification.depth == 0) {
_onScroll(notification.metrics.pixels);
}
}
// 返回 false: 事件继续向上传播
// 返回 true: 事件被消费,不再传播
return false;
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('列表项 $index'),
subtitle: Text('滚动距离: ${_scrollOffset.toStringAsFixed(0)} px'),
);
},
),
),
);
}
}
【示例 2】滚动方向检测 - 向上/向下滚动时隐藏/显示 FAB
php
/// 判断用户向上还是向下滚动,根据方向显示/隐藏浮动按钮
class ScrollDirectionDetectionDemo extends StatefulWidget {
const ScrollDirectionDetectionDemo({Key? key}) : super(key: key);
@override
State<ScrollDirectionDetectionDemo> createState() =>
_ScrollDirectionDetectionDemoState();
}
class _ScrollDirectionDetectionDemoState
extends State<ScrollDirectionDetectionDemo> {
bool _showFAB = true; // 是否显示浮动按钮
double _lastScrollOffset = 0; // 上次滚动位置
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('滚动方向检测'), centerTitle: true),
body: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
double currentOffset = notification.metrics.pixels;
// 判断滚动方向
// 当前位置 > 上次位置 = 向下滚动
// 当前位置 < 上次位置 = 向上滚动
if (currentOffset > _lastScrollOffset) {
// 向下滚动:隐藏 FAB
if (_showFAB) {
setState(() => _showFAB = false);
}
} else {
// 向上滚动:显示 FAB
if (!_showFAB) {
setState(() => _showFAB = true);
}
}
_lastScrollOffset = currentOffset;
}
return false;
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
title: Text('项目 $index'),
subtitle: const Text('向上/向下滚动,观察 FAB 变化'),
leading: CircleAvatar(child: Text('$index')),
),
);
},
),
),
// 根据 _showFAB 显示或隐藏浮动按钮
floatingActionButton: _showFAB
? FloatingActionButton(
onPressed: () {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('向上滚动时显示 FAB')));
},
child: const Icon(Icons.arrow_upward),
)
: null, // null 时不显示任何按钮
);
}
}
【示例 3】监听滚动到底部 - 实现加载更多功能
php
/// 检测用户是否滚动到列表底部,触发加载更多数据的操作
class ScrollToBottomDetectionDemo extends StatefulWidget {
const ScrollToBottomDetectionDemo({Key? key}) : super(key: key);
@override
State<ScrollToBottomDetectionDemo> createState() =>
_ScrollToBottomDetectionDemoState();
}
class _ScrollToBottomDetectionDemoState
extends State<ScrollToBottomDetectionDemo> {
bool _isAtBottom = false; // 是否已滚动到底部
int _loadMoreThreshold = 100; // 距离底部 100px 时触发加载
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('滚动到底部检测')),
body: Stack(
children: [
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
// 📌 ScrollMetrics 包含滚动的关键数据
final metrics = notification.metrics;
// maxScrollExtent: 最大可滚动距离
// pixels: 当前滚动距离
final isNearBottom =
metrics.pixels >=
(metrics.maxScrollExtent - _loadMoreThreshold);
if (isNearBottom != _isAtBottom) {
setState(() => _isAtBottom = isNearBottom);
if (isNearBottom) {
print('📢 已接近底部,可以加载更多数据');
// 在这里调用加载更多数据的 API
// loadMoreData();
}
}
}
return false;
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('列表项 $index'),
subtitle: const Text('滚动到底部自动加载更多'),
);
},
),
),
// 显示当前滚动状态
Positioned(
bottom: 20,
right: 20,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _isAtBottom ? Colors.red : Colors.green,
borderRadius: BorderRadius.circular(20),
),
child: Text(
_isAtBottom ? '✓ 到底部了' : '↓ 继续滚动',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
}
【示例 4】嵌套列表滚动事件处理 - 区分外层和内层事件
less
/// depth 属性用于区分哪一层列表发出的事件
class NestedScrollDetectionDemo extends StatelessWidget {
const NestedScrollDetectionDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('嵌套滚动事件处理')),
body: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
// 📌 depth 值的含义:
// depth == 0:最外层列表发出的事件
// depth == 1:第一层嵌套列表发出的事件
// depth == 2:第二层嵌套列表发出的事件
// ...以此类推
print(
'Depth: ${notification.depth}, '
'Pixels: ${notification.metrics.pixels.toStringAsFixed(0)}',
);
}
return false;
},
child: ListView(
children: [
// 【外层项目 1】
Container(
height: 150,
color: Colors.blue[100],
child: const Center(
child: Text(
'外层列表项 1\ndepth=0 的事件',
textAlign: TextAlign.center,
),
),
),
// 【内层水平列表】
SizedBox(
height: 120,
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
print('【内层列表】滚动距离: ${notification.metrics.pixels}');
}
return false;
},
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) {
return Container(
width: 100,
margin: const EdgeInsets.all(8),
color: Colors.green,
child: Center(child: Text('内层 $index')),
);
},
),
),
),
// 【外层项目 2】
Container(
height: 150,
color: Colors.purple[100],
child: const Center(
child: Text(
'外层列表项 2\ndepth=0 的事件',
textAlign: TextAlign.center,
),
),
),
],
),
),
);
}
}
【使用总结】
🎯 NotificationListener 的核心概念:
-
【onNotification 回调】
- 当监听的 Notification 事件发生时触发
- 返回 false:事件继续向上传播
- 返回 true:事件被消费,不再传播
-
【泛型指定】
- NotificationListener - 只监听滚动事件
- NotificationListener - 只监听尺寸变化
- NotificationListener - 监听所有 Notification
-
【depth 属性】
- 用于区分嵌套列表的事件来源
- depth == 0 是最外层
- 避免在嵌套列表中重复响应同一事件
-
【ScrollMetrics】
- pixels: 当前滚动距离
- maxScrollExtent: 最大可滚动距离
- viewportDimension: 视口高度
- axisDirection: 滚动轴方向
💡 常见应用场景:
- 实现 AppBar 渐显/隐藏效果
- 监听滚动方向,显示/隐藏 FAB
- 检测是否滚动到底部,加载更多数据
- 记录用户滚动行为进行分析
- 实现虚拟列表优化性能
- 同步多个列表的滚动位置