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
相关的关键状态和回调
- 数据传递:
observerWidgetState
是ObserverWidget(一个 StatefulWidget)
的State
对象,封装了观察逻辑的核心状态,如当前的观察数据模型 (M)、控制器 (C) 等。通过 ObserverWidgetScope,这些状态可以被树中更深层的子 Widget 访问到,而无需通过构造函数逐层传递。 - 回调机制:
onCreateElement
回调函数会在ObserverWidgetScope
对应的InheritedElement
被创建时调用。提供了一个时机,让ObserverWidget
或其状态对象能够获取到这个InheritedElement
的引用,ObserverWidgetState
可能需要直接与这个InheritedElement
交互来触发某些更新或获取上下文信息。 - 泛型约束: 使用了多个泛型参数
(C, M, N, T)
并对它们进行了约束。ObserverWidgetScope
非常灵活,可以适配不同类型的控制器、数据模型、通知和观察者组件,同时保持类型安全。 - 更新通知:
updateShouldNotify
方法中,当observerWidgetState
引用发生变化时,是否需要通知那些依赖于它的子 Widget 进行重建。这是 InheritedWidget 的核心机制,确保了当共享状态更新时,UI 能够自动响应。
observer_widget.dart
- 观察子组件
models(dir)
ObserveDisplayingChildModel
:观察正在展示组件的类,包含renderSliver/viewPort/index/renderObject
ObserveDisplayingChildModelMixin
:ObserveDisplayingChildModel
的混入,获取各种属性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 | 表示布局发生变化的通知,供上层监听并处理布局变更的影响 |
典型应用场景
在
ListView
、GridView
、NestedScrollView
中监听滚动事件:dartNotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification) { // 根据滚动位置更新 UI return false; // 不拦截,继续冒泡 }, child: ListView(...), );
在某些子
Widget
大小或布局发生变化时,通过LayoutChangedNotification
通知上层。
BuildContext
抽象类的作用
BuildContext
是Widget
在Widget
树中的“位置句柄”,用于访问组件上下文、获取父组件、查找依赖、调度通知等。
dart
/// 在给定的 build context 上开始冒泡(传播)这个通知。
///
/// 当调用 dispatch 或类似方法时,通知会从当前组件(通过 BuildContext 定位)开始,
/// 沿着组件树向上层传递,直到被匹配类型的 NotificationListener 捕获或到达根组件
void dispatchNotification(Notification notification);