flutter中 NotificationListener 详细使用指南

NotificationListener 详细使用指南

📌 什么是 NotificationListener? NotificationListener 是 Flutter 中用于监听各种系统通知事件的组件。 当列表滚动、键盘显示、尺寸变化等事件发生时,系统会向上传播相应的 Notification。

  • ✨ 主要特点:
  1. 可以捕获和拦截各种 Notification 事件
  2. 通过 onNotification 回调处理事件
  3. 返回 true 表示消费事件(不再向上传播),返回 false 表示不消费
  4. 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 的核心概念:

  1. 【onNotification 回调】

    • 当监听的 Notification 事件发生时触发
    • 返回 false:事件继续向上传播
    • 返回 true:事件被消费,不再传播
  2. 【泛型指定】

    • NotificationListener - 只监听滚动事件
    • NotificationListener - 只监听尺寸变化
    • NotificationListener - 监听所有 Notification
  3. 【depth 属性】

    • 用于区分嵌套列表的事件来源
    • depth == 0 是最外层
    • 避免在嵌套列表中重复响应同一事件
  4. 【ScrollMetrics】

    • pixels: 当前滚动距离
    • maxScrollExtent: 最大可滚动距离
    • viewportDimension: 视口高度
    • axisDirection: 滚动轴方向

💡 常见应用场景:

  • 实现 AppBar 渐显/隐藏效果
  • 监听滚动方向,显示/隐藏 FAB
  • 检测是否滚动到底部,加载更多数据
  • 记录用户滚动行为进行分析
  • 实现虚拟列表优化性能
  • 同步多个列表的滚动位置
相关推荐
大杯咖啡5 小时前
一篇文章搞懂,浏览器强缓存以及协商缓存
前端·javascript
王六岁5 小时前
# 🐍 前端开发 0 基础学 Python 入门指南: Python 元组和映射类型深入指南
前端·javascript·python
_志哥_5 小时前
多行文本超出,中间显示省略号的终极方法(适配多语言)
前端·javascript·vue.js
王六岁5 小时前
# 🐍 前端开发 0 基础学 Python 入门指南:常用的数据类型和列表
前端·javascript·python
1_2_3_5 小时前
神级JS API,谁用谁好用
前端·javascript
冬至已至5 小时前
AI 时代的自动化信息获取与整合
前端
我是日安5 小时前
从零到一打造 Vue3 响应式系统 Day 29 - readonly:数据保护实现
前端·javascript·vue.js
时代拖油瓶5 小时前
我劝你必须知道——Intl.Segmenter
前端·javascript
海在掘金611275 小时前
从"万能函数"到"精准工具":泛型如何消除重复代码
前端