公告

微信

欢迎大家私信交流

Skip to content

scrollview_observer分析

结构

├── scrollview_observer.dart
└── src
    ├── common
    │   ├── models
    │   │   ├── observe_displaying_child_model.dart
    │   │   ├── observe_displaying_child_model_mixin.dart
    │   │   ├── observe_find_child_model.dart
    │   │   ├── observe_model.dart
    │   │   ├── observe_scroll_child_model.dart
    │   │   ├── observe_scroll_to_index_result_model.dart
    │   │   ├── observer_handle_contexts_result_model.dart
    │   │   └── observer_index_position_model.dart
    │   ├── observer_controller.dart
    │   ├── observer_listener.dart
    │   ├── observer_notification_result.dart
    │   ├── observer_typedef.dart
    │   ├── observer_widget.dart
    │   ├── observer_widget_scope.dart
    │   ├── observer_widget_tag_manager.dart
    │   └── typedefs.dart
    ├── gridview
    │   ├── grid_observer_controller.dart
    │   ├── grid_observer_notification_result.dart
    │   ├── grid_observer_view.dart
    │   └── models
    │       ├── gridview_observe_displaying_child_model.dart
    │       └── gridview_observe_model.dart
    ├── listview
    │   ├── list_observer_controller.dart
    │   ├── list_observer_notification_result.dart
    │   ├── list_observer_view.dart
    │   └── models
    │       ├── listview_observe_displaying_child_model.dart
    │       └── listview_observe_model.dart
    ├── notification.dart
    ├── observer_core.dart
    ├── sliver
    │   ├── models
    │   │   ├── sliver_observer_observe_result_model.dart
    │   │   ├── sliver_viewport_observe_displaying_child_model.dart
    │   │   └── sliver_viewport_observe_model.dart
    │   ├── sliver_observer_controller.dart
    │   ├── sliver_observer_listener.dart
    │   ├── sliver_observer_notification_result.dart
    │   └── sliver_observer_view.dart
    └── utils
        ├── observer_utils.dart
        └── src
            ├── chat
            │   ├── chat_observer_scroll_physics.dart
            │   ├── chat_observer_scroll_physics_mixin.dart
            │   ├── chat_scroll_observer.dart
            │   ├── chat_scroll_observer_model.dart
            │   └── chat_scroll_observer_typedefs.dart
            ├── extends.dart
            ├── log.dart
            ├── nested_scroll_util.dart
            ├── observer_utils.dart
            └── slivers.dart

13 directories, 47 files

分析

notification.dart

代码分析

ScrollViewOnceObserveNotification,继承Notification。该类主要用于 配置是否可以强制展示观察结果、是否观察结果出来后直接回调。

ObserverScrollNotification,继承Notification。该类定义观察滚动的任务。

[ObserverScrollStartNotification] -> [ObserverScrollDecisionNotification] -> [ObserverScrollEndNotification],以上都继承ObserverScrollNotification

observer_core.dart

代码分析

ObserverCore中有两个静态方法:

  • handleListObserve:处理与 [SliverList] 类似的 Sliver 的观测逻辑 (deepseek分析)
dart
/// 处理类似SliverList的Sliver组件的观测逻辑,返回当前可视区域内的子项信息
/// 
/// [context]: 当前组件的BuildContext
/// [fetchLeadingOffset]: 可选,获取额外前导偏移量的回调函数
/// [customOverlap]: 可选,自定义重叠区域计算的回调函数
/// [toNextOverPercent]: 判断进入可视区域的阈值比例(0-1),默认为1(完全进入)
static ListViewObserveModel? handleListObserve({
  required BuildContext context,
  double Function()? fetchLeadingOffset,
  double? Function(BuildContext)? customOverlap,
  double toNextOverPercent = 1,
}) {
  // 1. 获取当前组件的渲染对象
  var _obj = ObserverUtils.findRenderObject(context);
  if (_obj is! RenderSliverMultiBoxAdaptor) return null; // 类型检查:必须是多盒子适配的Sliver
  
  // 2. 查找关联的视口(viewport)
  final viewport = ObserverUtils.findViewport(_obj);
  if (viewport == null) return null; // 无关联视口则退出
  
  // 调试模式下检查是否需要重绘
  if (kDebugMode) {
    if (viewport.debugNeedsPaint) return null; // 如果需要重绘则暂不处理
  }
  
  // 3. 检查Sliver是否可见(注意:geometry.visible不完全可靠)
  if (!(_obj.geometry?.visible ?? false) ||      // 几何信息不可见
      _obj.constraints.remainingPaintExtent < 1e-10) {  // 剩余绘制范围极小
    return ListViewObserveModel(  // 返回不可见状态的空模型
      sliverList: _obj,
      viewport: viewport,
      visible: false,
      firstChild: null,
      displayingChildModelList: [],
      displayingChildModelMap: {},
    );
  }
  
  // 4. 准备基础数据
  final scrollDirection = _obj.constraints.axis;  // 获取滚动方向(垂直/水平)
  var firstChild = _obj.firstChild;  // 获取第一个子项
  if (firstChild == null) return null;  // 无子项则退出

  // 5. 计算滚动偏移量(考虑自定义偏移和重叠)
  final offset = fetchLeadingOffset?.call() ?? 0;  // 获取自定义前导偏移
  final overlap = customOverlap?.call(context) ?? _obj.constraints.overlap;  // 获取重叠区域
  final rawScrollViewOffset = _obj.constraints.scrollOffset + overlap;  // 原始滚动偏移
  var scrollViewOffset = rawScrollViewOffset + offset;  // 最终计算偏移
  
  // 6. 获取第一个子项的索引
  var parentData = firstChild.parentData as SliverMultiBoxAdaptorParentData;
  var index = parentData.index ?? 0;

  // 7. 查找第一个可见的子项
  bool isNotFound = false;  // 是否找到可见项的标记
  var targetFirstChild = firstChild;  // 当前检查的子项
  
  // 循环查找第一个满足条件的子项
  while (!ObserverUtils.isBelowOffsetWidgetInSliver(
    scrollViewOffset: scrollViewOffset,
    scrollDirection: scrollDirection,
    targetChild: targetFirstChild,
    toNextOverPercent: toNextOverPercent,
  )) {
    index = index + 1;  // 索引递增
    var nextChild = _obj.childAfter(targetFirstChild);  // 获取下一个子项
    if (nextChild == null) {  // 没有更多子项
      isNotFound = true;
      break;
    }

    // 处理分隔符情况(非RenderIndexedSemantics对象)
    if (nextChild is! RenderIndexedSemantics) {
      nextChild = _obj.childAfter(nextChild);  // 跳过分隔符
    }
    if (nextChild == null) {  // 跳过分隔符后没有更多子项
      isNotFound = true;
      break;
    }
    targetFirstChild = nextChild;  // 继续检查下一个子项
  }

  // 8. 如果没有找到可见子项
  if (isNotFound) {
    return ListViewObserveModel(  // 返回不可见状态的空模型
      sliverList: _obj,
      viewport: viewport,
      visible: false,
      firstChild: null,
      displayingChildModelList: [],
      displayingChildModelMap: {},
    );
  }

  // 9. 类型检查确保是有效的内容子项
  if (targetFirstChild is! RenderIndexedSemantics) return null;

  // 10. 创建第一个可见子项的模型
  final firstDisplayingChildIndex = targetFirstChild.index;
  final firstDisplayingChildModel = ListViewObserveDisplayingChildModel(
    sliverList: _obj,
    viewport: viewport,
    index: firstDisplayingChildIndex,
    renderObject: targetFirstChild,
  );
  
  // 11. 初始化可见子项集合
  Map<int, ListViewObserveDisplayingChildModel> displayingChildModelMap = {
    firstDisplayingChildIndex: firstDisplayingChildModel,
  };
  List<ListViewObserveDisplayingChildModel> displayingChildModelList = [
    firstDisplayingChildModel,
  ];

  // 12. 查找剩余可见子项
  final showingChildrenMaxOffset =  // 计算最大显示范围
      rawScrollViewOffset + _obj.constraints.remainingPaintExtent - overlap;
  var displayingChild = _obj.childAfter(targetFirstChild);  // 从第一个可见项后开始
  
  // 循环检查后续子项是否可见
  while (ObserverUtils.isDisplayingChildInSliver(
    targetChild: displayingChild,
    showingChildrenMaxOffset: showingChildrenMaxOffset,
    scrollViewOffset: scrollViewOffset,
    scrollDirection: scrollDirection,
    toNextOverPercent: toNextOverPercent,
  )) {
    if (displayingChild == null) {  // 没有更多子项
      break;
    }
    if (displayingChild is! RenderIndexedSemantics) {  // 跳过分隔符
      displayingChild = _obj.childAfter(displayingChild);
      continue;
    }

    // 创建可见子项模型并加入集合
    final displayingChildIndex = displayingChild.index;
    final displayingChildModel = ListViewObserveDisplayingChildModel(
      sliverList: _obj,
      viewport: viewport,
      index: displayingChildIndex,
      renderObject: displayingChild,
    );
    displayingChildModelList.add(displayingChildModel);
    displayingChildModelMap[displayingChildIndex] = displayingChildModel;
    displayingChild = _obj.childAfter(displayingChild);  // 继续检查下一个
  }

  // 13. 返回最终的可视项观测模型
  return ListViewObserveModel(
    sliverList: _obj,
    viewport: viewport,
    visible: true,
    firstChild: firstDisplayingChildModel,
    displayingChildModelList: displayingChildModelList,
    displayingChildModelMap: displayingChildModelMap,
  );
}
  • handleGridObserve:处理与 [SliverGrid] 类似的 Sliver 的观测逻辑(gpt分析)
dart
static GridViewObserveModel? handleGridObserve({
  required BuildContext context,
  double Function()? fetchLeadingOffset, // 可选的额外偏移量
  double? Function(BuildContext)? customOverlap, // 可选的 overlap 计算方法
  double toNextOverPercent = 1, // 控制判断是否达到下一个 item 的比例
}) {
  // 查找当前 context 对应的 RenderObject
  final _obj = ObserverUtils.findRenderObject(context);
  if (_obj is! RenderSliverMultiBoxAdaptor) return null;

  // 获取所属的 Viewport(用于判断可视区域等)
  final viewport = ObserverUtils.findViewport(_obj);
  if (viewport == null) return null;

  // Debug 模式下,如果还没准备好绘制,直接返回 null
  if (kDebugMode) {
    if (viewport.debugNeedsPaint) return null;
  }

  // 如果不可见或 paint 区域极小,则认为当前 grid 不在屏幕中显示
  if (!(_obj.geometry?.visible ?? false) ||
      _obj.constraints.remainingPaintExtent < 1e-10) {
    return GridViewObserveModel(
      sliverGrid: _obj,
      viewport: viewport,
      visible: false,
      firstGroupChildList: [],
      displayingChildModelList: [],
      displayingChildModelMap: {},
    );
  }

  // 获取滚动方向
  final scrollDirection = _obj.constraints.axis;

  // 获取第一个子节点
  var firstChild = _obj.firstChild;
  if (firstChild == null) return null;

  // 计算实际偏移量 = scrollOffset + overlap + 额外偏移
  final offset = fetchLeadingOffset?.call() ?? 0;
  final overlap = customOverlap?.call(context) ?? _obj.constraints.overlap;
  final rawScrollViewOffset = _obj.constraints.scrollOffset + overlap;
  var scrollViewOffset = rawScrollViewOffset + offset;

  bool isNotFound = false; // 标记是否找到目标 child
  var targetFirstChild = firstChild;
  var lastFirstGroupChildWidget = targetFirstChild;

  // 查找首个进入视图范围的 child(可视区域内的第一个 item)
  while (!ObserverUtils.isBelowOffsetWidgetInSliver(
    scrollViewOffset: scrollViewOffset,
    scrollDirection: scrollDirection,
    targetChild: targetFirstChild,
    toNextOverPercent: toNextOverPercent,
  )) {
    // 说明当前 child 还未达到显示阈值,继续找下一个
    RenderBox? nextChild = _obj.childAfter(targetFirstChild);
    if (nextChild == null) {
      isNotFound = true;
      break;
    }
    targetFirstChild = nextChild;
  }

  // 如果没有找到可视区域的 child,则返回不可见模型
  if (isNotFound) {
    return GridViewObserveModel(
      sliverGrid: _obj,
      viewport: viewport,
      visible: false,
      firstGroupChildList: [],
      displayingChildModelList: [],
      displayingChildModelMap: {},
    );
  }

  // 如果找到的不是 Grid item,返回 null
  if (targetFirstChild is! RenderIndexedSemantics) return null;

  lastFirstGroupChildWidget = targetFirstChild;

  // 构造第一个可见 item 的 model
  final firstDisplayingChildIndex = targetFirstChild.index;
  final firstModel = GridViewObserveDisplayingChildModel(
    sliverGrid: _obj,
    viewport: viewport,
    index: firstDisplayingChildIndex,
    renderObject: targetFirstChild,
  );

  Map<int, GridViewObserveDisplayingChildModel> displayingChildModelMap = {
    firstDisplayingChildIndex: firstModel,
  };
  List<GridViewObserveDisplayingChildModel> firstGroupChildModelList = [
    firstModel,
  ];

  // 计算最大可视范围 offset(bottom)
  final showingChildrenMaxOffset =
      rawScrollViewOffset + _obj.constraints.remainingPaintExtent - overlap;

  // 找出首个 group 中也达到阈值的其他 child(通常是同一行/列)
  RenderBox? targetChild = _obj.childAfter(targetFirstChild);
  while (targetChild != null) {
    if (ObserverUtils.isReachOffsetWidgetInSliver(
      scrollViewOffset: max(scrollViewOffset, firstModel.layoutOffset),
      scrollDirection: scrollDirection,
      targetChild: targetChild,
      toNextOverPercent: toNextOverPercent,
    )) {
      if (targetChild is! RenderIndexedSemantics) break;
      final targetChildIndex = targetChild.index;
      final displayingChildModel = GridViewObserveDisplayingChildModel(
        sliverGrid: _obj,
        viewport: viewport,
        index: targetChildIndex,
        renderObject: targetChild,
      );
      firstGroupChildModelList.add(displayingChildModel);
      displayingChildModelMap[targetChildIndex] = displayingChildModel;
      lastFirstGroupChildWidget = targetChild;
    }

    RenderBox? nextChild = _obj.childAfter(targetChild);
    if (nextChild == null) break;
    targetChild = nextChild;
  }

  // 复制 first group 的可见 model 列表作为初始 showing 列表
  List<GridViewObserveDisplayingChildModel> showingChildModelList =
      List.from(firstGroupChildModelList);

  // 继续查找剩余的可视 child(首 group 之后的)
  var displayingChild = _obj.childAfter(lastFirstGroupChildWidget);
  while (displayingChild != null) {
    if (ObserverUtils.isDisplayingChildInSliver(
      targetChild: displayingChild,
      showingChildrenMaxOffset: showingChildrenMaxOffset,
      scrollViewOffset: scrollViewOffset,
      scrollDirection: scrollDirection,
      toNextOverPercent: toNextOverPercent,
    )) {
      if (displayingChild is! RenderIndexedSemantics) {
        // 不是实际 item(可能是 separator),跳过
        continue;
      }
      final displayingChildIndex = displayingChild.index;
      final displayingChildModel = GridViewObserveDisplayingChildModel(
        sliverGrid: _obj,
        viewport: viewport,
        index: displayingChildIndex,
        renderObject: displayingChild,
      );
      showingChildModelList.add(displayingChildModel);
      displayingChildModelMap[displayingChildIndex] = displayingChildModel;
    }
    displayingChild = _obj.childAfter(displayingChild);
  }

  // 返回结果模型,包括是否可见、首组 item、全部可见 item 列表等
  return GridViewObserveModel(
    sliverGrid: _obj,
    viewport: viewport,
    visible: true,
    firstGroupChildList: firstGroupChildModelList,
    displayingChildModelList: showingChildModelList,
    displayingChildModelMap: displayingChildModelMap,
  );
}

common(dir)

typedefs.dart

  • ObserverWidgetObserveResultType:观察组件结果类型

observer_widget_tag_manager.dart

ObserverWidgetTagManager继承了InheritedWidget,可以在widget树中向下传递信息。 当InheritedWidget数据发生变化,依赖该组件的子组件可以自动重建。

该类的功能:

  • 管理标签和上下文映射map
  • 通过tag设置/移除/获取上下文
  • 子组件通过maybeOf静态方法访问该类
  • 控制更新通知

作用:

  • 标记可观察的Widget: 给滚动视图中的某些特定子Widget打上标签
  • 通过标签定位Widget: 需要滚动到某个特定的子Widget时,如果该Widget已经被标记,可以通过它的标签从ObserverWidgetTagManager中获取到它的 BuildContext
  • 传递上下文信息: 在Widget树的不同部分访问和操作这些被标记的Widget的上下文信息,不需要通过构造函数层层传递

observer_widget_scope.dart

ObserverWidgetScope继承了InheritedWidget,在Widget树中有效地传递与ObserverWidget相关的关键状态和回调

  1. 数据传递: observerWidgetStateObserverWidget(一个 StatefulWidget)State对象,封装了观察逻辑的核心状态,如当前的观察数据模型 (M)、控制器 (C) 等。通过 ObserverWidgetScope,这些状态可以被树中更深层的子 Widget 访问到,而无需通过构造函数逐层传递。
  2. 回调机制: onCreateElement回调函数会在ObserverWidgetScope对应的InheritedElement被创建时调用。提供了一个时机,让ObserverWidget或其状态对象能够获取到这个InheritedElement的引用,ObserverWidgetState可能需要直接与这个InheritedElement交互来触发某些更新或获取上下文信息。
  3. 泛型约束: 使用了多个泛型参数(C, M, N, T)并对它们进行了约束。ObserverWidgetScope非常灵活,可以适配不同类型的控制器、数据模型、通知和观察者组件,同时保持类型安全。
  4. 更新通知: updateShouldNotify方法中,当observerWidgetState引用发生变化时,是否需要通知那些依赖于它的子 Widget 进行重建。这是 InheritedWidget 的核心机制,确保了当共享状态更新时,UI 能够自动响应。

observer_widget.dart

  1. 观察子组件

models(dir)

  • ObserveDisplayingChildModel:观察正在展示组件的类,包含renderSliver/viewPort/index/renderObject
  • ObserveDisplayingChildModelMixinObserveDisplayingChildModel的混入,获取各种属性
  • ObserveFindChildModel:内部传递数据的类,包含renderSliver/viewPort/index/renderObject
  • ObserveModel:返回观察的结果的类,包含是否可见/渲染sliver/视窗/innerDisplayingChildModelList/innerDisplayingChildModelMap
  • ObserveScrollChildModel:滚动子组件相关信息类,包含大小/展示偏移量
  • ObserveScrollToIndexFixedHeightResultModel:固定高度项,滚动到指定索引项后的信息类,包含主轴子项大小/子项间距/目标所在行索引/目标子组件在主轴上的偏移量
  • ObservePrepareScrollToIndexModel:滚动到指定索引项的信息类,包含当前sliver之前的sliver滚动长度/目标布局的偏移量/目标子组件在主轴的偏移量
  • ObserverHandleContextsResultModel:处理观察滚动上下文结果的信息类,包含单个上下文的观察结果/多个上下文的观察结果Map
  • ObserverIndexPositionModel:滚动到目标位置的详细信息,包含索引/该滚动项所在上下文/是够固定高度/对齐方式(在视图的相对位置)/偏移回调/内边距

补充

Notification类的作用

Widget树中自下而上(bubbling)传递消息,用于在子组件向上通知父组件某些状态变化,比如滚动、布局变化等。

关键类功能简述

类/类型简要说明
Notification抽象类,表示一个可以在Widget树中冒泡传递的通知(如滚动、布局变更)
dispatch(BuildContext)方法:通知从指定 BuildContext 开始向上传递
NotificationListener<T>监听特定类型 T 的通知,处理方式定义在 onNotification 回调中
NotificationListenerCallback<T>回调类型,接收通知并返回是否“拦截”通知(返回 true 表示停止冒泡)
_NotificationElement<T>内部类,承载 NotificationListener 元素并实现通知处理逻辑
LayoutChangedNotification表示布局发生变化的通知,供上层监听并处理布局变更的影响

典型应用场景

  • ListViewGridViewNestedScrollView 中监听滚动事件:

    dart
    NotificationListener<ScrollNotification>(
      onNotification: (ScrollNotification notification) {
        // 根据滚动位置更新 UI
        return false; // 不拦截,继续冒泡
      },
      child: ListView(...),
    );
  • 在某些子Widget大小或布局发生变化时,通过 LayoutChangedNotification 通知上层。

BuildContext抽象类的作用

BuildContextWidgetWidget树中的“位置句柄”,用于访问组件上下文、获取父组件、查找依赖、调度通知等。

dart
/// 在给定的 build context 上开始冒泡(传播)这个通知。
///
/// 当调用 dispatch 或类似方法时,通知会从当前组件(通过 BuildContext 定位)开始,
/// 沿着组件树向上层传递,直到被匹配类型的 NotificationListener 捕获或到达根组件
void dispatchNotification(Notification notification);

RenderSliver抽象类作用

上次更新于: