handleScroll method
Implementation
bool handleScroll(ScrollNotification n) {
if (canScroll != null && !canScroll!()) return false;
// 只响应直接子滚动体的竖向滚动:页面内嵌套的横向/内层列表(depth > 0)
// 不得驱动顶栏折叠,否则横滑一个内嵌列表也会牵动大标题。
if (n.depth != 0 || n.metrics.axis != Axis.vertical) return false;
final metrics = n.metrics;
final pixels = metrics.pixels;
final minExtent = metrics.minScrollExtent;
final maxExtent = metrics.maxScrollExtent;
// 短页面:最大可滚行程小于展开量,标题只能靠橡皮筋越界折叠,而越界
// 松手必回弹——通用的位置同步会在回弹期间把标题重新拉回展开。走专用
// 路径:回弹期冻结,松手后按阈值吸附到端点。
final limit = state.heightOffsetLimit;
final expansion = limit.isFinite ? -limit : 0.0;
final canParkViaScroll =
expansion <= 0 || (maxExtent - minExtent) >= expansion - 1.0;
if (!canParkViaScroll) {
return _handleShortPageScroll(n);
}
if (_snapInProgress && n is! ScrollEndNotification) {
// 吸附动画运行期间仍按位置同步折叠量(保持 offset ≡ f(pixels))。
_syncOffsetToPosition(metrics);
return false;
}
// 松手时列表仍在底部越界:后续回弹动画期间冻结标题。
if (n is ScrollEndNotification &&
pixels > maxExtent + 0.5 &&
state.heightOffset < 0) {
_frozenDuringOverscrollSpringBack = true;
}
// 新手势开始 → 清除冻结与小标题锁。
if (n is ScrollStartNotification) {
_frozenDuringOverscrollSpringBack = false;
_smallTitleLocked = false;
}
if (_frozenDuringOverscrollSpringBack) {
state.contentOffset = pixels;
if (pixels <= maxExtent + 0.5) {
// 回弹结束;重新同步一次并清除标志。
_frozenDuringOverscrollSpringBack = false;
_syncOffsetToPosition(metrics);
}
return false;
}
if (_smallTitleLocked) {
// 当前手势/惯性期间保持小标题冻结。
state.contentOffset = pixels;
return false;
}
_syncOffsetToPosition(metrics);
// 可选增强:折叠后锁定小标题,直到新手势。
if (lockSmallTitleUntilTop &&
state.heightOffset < -5.0 &&
pixels > 0.5) {
_smallTitleLocked = true;
}
if (snapOnRelease && n is ScrollEndNotification) {
_snapToNearestEndpoint(n);
}
return false;
}