Flutter.源码分析 ScrollView flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ScrollView


李俊才 的个人博客blog.csdn.net/qq_28550263

本文地址blog.csdn.net/qq_28550263…

本文供给Flutter结构中ScrollView类源码注释的中文翻译以及必要的分析说明。


目 录


1. 类注释部分

/// 一个组合了 [Scrollable] 和 [Viewport] 的组件,用于在一个维度上创立一个可交互的翻滚内容窗格。
///
/// 可翻滚组件由三部分组成:
///
///  1. 一个 [Scrollable] 组件,它监听各种用户手势并完成翻滚的交互设计。
///  2. 一个视口组件,如 [Viewport] 或 [ShrinkWrappingViewport],它经过仅显现翻滚视图内部的部分组件来完成翻滚的视觉设计。
///  3. 一个或多个 slivers,这些组件能够组合起来创立各种翻滚作用,如列表、网格和打开的头部。
///
/// [ScrollView] 经过创立 [Scrollable] 和视口,并将创立 slivers 的使命托付给其子类,来和谐这些部分。
///
/// 要了解更多关于 slivers 的信息,请参看 [CustomScrollView.slivers]。
///
/// 要操控翻滚视图的初始翻滚偏移量,供给一个设置了 [ScrollController.initialScrollOffset] 特点的 [controller]。
///
/// 另请参看:
///
///  * [ListView],这是一个常用的 [ScrollView],显现一个翻滚的、线性的子组件列表。
///  * [PageView],这是一个翻滚的子组件列表,每个子组件都是视口的巨细。
///  * [GridView],这是一个 [ScrollView],显现一个翻滚的、二维的子组件数组
///  * [CustomScrollView],这是一个 [ScrollView],运用 slivers 创立自界说翻滚作用。
///  * [ScrollNotification] 和 [NotificationListener],它们能够用来调查翻滚方位,而无需运用 [ScrollController]。
///  * [TwoDimensionalScrollView],这是一个相似的组件 [ScrollView],它在两个维度上翻滚。
abstract class ScrollView extends StatelessWidget {

2. 结构办法部分

  /// 创立一个能够翻滚的组件。
  ///
  /// 假如没有供给 [controller],则 [ScrollView.primary] 参数默许为笔直翻滚视图的 true。假如 [primary] 清晰设置为 true,则 [controller] 参数有必要为 null。假如 [primary] 为 true,则将最近的围住组件的 [PrimaryScrollController] 附加到此翻滚视图。
  ///
  /// 假如 [shrinkWrap] 参数为 true,则 [center] 参数有必要为 null。
  ///
  /// [scrollDirection]、[reverse] 和 [shrinkWrap] 参数有必要不为 null。
  ///
  /// [anchor] 参数有必要为非 null,而且在 0.0 到 1.0 的范围内。
  const ScrollView({
    super.key,
    this.scrollDirection = Axis.vertical,
    this.reverse = false,
    this.controller,
    this.primary,
    ScrollPhysics? physics,
    this.scrollBehavior,
    this.shrinkWrap = false,
    this.center,
    this.anchor = 0.0,
    this.cacheExtent,
    this.semanticChildCount,
    this.dragStartBehavior = DragStartBehavior.start,
    this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    this.restorationId,
    this.clipBehavior = Clip.hardEdge,
  }) : assert(
         !(controller != null && (primary ?? false)),
         'Primary ScrollViews obtain their ScrollController via inheritance '
         'from a PrimaryScrollController widget. You cannot both set primary to '
         'true and pass an explicit controller.',
       ),
       assert(!shrinkWrap || center == null),
       assert(anchor >= 0.0 && anchor <= 1.0),
       assert(semanticChildCount == null || semanticChildCount >= 0),
       physics = physics ?? ((primary ?? false) || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null);

3. scrollDirection 特点部分

/// {@template flutter.widgets.scroll_view.scrollDirection}
/// 翻滚视图偏移量增加的 [Axis]。
///
/// 关于可能产生活动翻滚的方向,请拜见 [ScrollDirection]。
///
/// 默许为 [Axis.vertical]。
/// {@endtemplate}
final Axis scrollDirection;

4. reverse 特点部分

  /// {@template flutter.widgets.scroll_view.reverse}
  /// 翻滚视图是否按阅读方向翻滚。
  ///
  /// 例如,假如阅读方向是从左到右,且 [scrollDirection] 为 [Axis.horizontal],
  /// 那么当 [reverse] 为 false 时,翻滚视图从左向右翻滚,当 [reverse] 为 true 时,从右向左翻滚。
  ///
  /// 相似地,假如 [scrollDirection] 为 [Axis.vertical],那么当 [reverse] 为 false 时,
  /// 翻滚视图从上向下翻滚,当 [reverse] 为 true 时,从下向上翻滚。
  ///
  /// 默许为 false。
  /// {@endtemplate}
  final bool reverse;

5. controller 特点部分

/// {@template flutter.widgets.scroll_view.controller}
/// 可用于操控翻滚视图翻滚到哪个方位的目标。
///
/// 假如 [primary] 为 true,则有必要为 null。
///
/// [ScrollController] 有多个用途。它能够用来操控初始翻滚方位(拜见 [ScrollController.initialScrollOffset])。
/// 它能够用来操控翻滚视图是否应主动在 [PageStorage] 中保存和康复其翻滚方位(拜见 [ScrollController.keepScrollOffset])。
/// 它能够用来读取当前翻滚方位(拜见 [ScrollController.offset]),或改动它(拜见 [ScrollController.animateTo])。
/// {@endtemplate}
final ScrollController? controller;

6. primary特点部分

/// {@template flutter.widgets.scroll_view.primary}
/// 是否是与父 [PrimaryScrollController] 相关的主翻滚视图。
///
/// 当此值为 true 时,即便翻滚视图没有满足的内容实际翻滚,也能够翻滚。不然,默许情况下,用户只要在视图有满足的内容时才干翻滚。拜见 [physics]。
///
/// 同样,当为 true 时,翻滚视图用于默许的 [ScrollAction]。假如 ScrollAction 没有被运用程序的其他聚集部分处理,
/// 则将运用此翻滚视图评价 ScrollAction,例如,履行 [Shortcuts] 键事情,如页面上下。
///
/// 在 iOS 上,这还标识了将呼应状况栏点击而翻滚到顶部的翻滚视图。
///
/// 不能在供给 `controller` 的 [ScrollController] 时为 true,只要一个 ScrollController 能够与 ScrollView 相关。
///
/// 设置为 false 将清晰阻止承继任何 [PrimaryScrollController]。
///
/// 默许为 null。当为 null,且没有供给操控器时,运用 [PrimaryScrollController.shouldInherit] 决议主动承继。
///
/// 默许情况下,每个 [ModalRoute] 注入的 [PrimaryScrollController] 都装备为在 [TargetPlatformVariant.mobile] 上主动承继
/// [Axis.vertical] 翻滚方向的 ScrollViews。在您的运用中增加另一个将覆盖其上方的 PrimaryScrollController。
///
/// 以下视频包括有关翻滚操控器、PrimaryScrollController 组件及其对您的运用的影响的更多信息:
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=33_0ABjFJUU}
///
/// {@endtemplate}
final bool? primary;

从注释中能够了解到:
primary 特点决议了 ScrollView 是否是与父 PrimaryScrollController 相关的主翻滚视图。

当 primary 特点为 true 时,即便翻滚视图没有满足的内容能够实际翻滚,也能够翻滚。不然,默许情况下,用户只要在视图有满足的内容时才干翻滚。

此外,当 primary 为 true 时,翻滚视图用于默许的 ScrollAction。假如 ScrollAction 没有被运用程序的其他聚集部分处理,那么将运用此翻滚视图评价 ScrollAction,例如,履行 Shortcuts 键事情,如页面上下。

在 iOS 上,primary 为 true 还标识了将呼应状况栏点击而翻滚到顶部的翻滚视图。

注意,不能在供给 controller 的 ScrollController 时将 primary 设置为 true,由于只要一个 ScrollController 能够与 ScrollView 相关。

设置 primary 为 false 将清晰阻止承继任何 PrimaryScrollController。

primary 的默许值为 null。当 primary 为 null,且没有供给操控器时,将运用 PrimaryScrollController.shouldInherit 决议是否主动承继。

默许情况下,每个 ModalRoute 注入的 PrimaryScrollController 都装备为在 TargetPlatformVariant.mobile 上主动承继 Axis.vertical 翻滚方向的 ScrollViews。在您的运用中增加另一个 PrimaryScrollController 将覆盖其上方的 PrimaryScrollController。

7. physics特点部分

/// {@template flutter.widgets.scroll_view.physics}
/// 翻滚视图应怎么呼运用户输入。
///
/// 例如,确认用户停止拖动翻滚视图后,翻滚视图怎么继续动画。
///
/// 默许为匹配渠道约定。此外,假如 [primary] 为 false,那么用户只要在有满足的内容能够翻滚时才干翻滚,
/// 而假如 [primary] 为 true,他们总是能够测验翻滚。
///
/// 要强制翻滚视图始终能够翻滚,即便没有满足的内容,就像 [primary] 为 true 相同,但不必定要将其设置为 true,
/// 供给一个 [AlwaysScrollableScrollPhysics] 物理目标,如下所示:
///
/// ```dart
///   physics: const AlwaysScrollableScrollPhysics(),
/// ```
///
///
/// 要强制翻滚视图运用默许的渠道约定,而且假如内容缺乏,不管 [primary] 的值怎么,都不可翻滚,
/// 供给一个清晰的 [ScrollPhysics] 目标,如下所示:
///
/// ```dart
///   physics: const ScrollPhysics(),
/// ```
///
/// 物理能够动态地改动(经过在后续的构建中供给一个新的目标),但新的物理只要在供给的目标的 _类_ 改动时才会收效。
/// 只是结构一个具有不同装备的新实例是缺乏以使物理从头运用的。 (这是由于终究运用的目标是动态生成的,
/// 这可能相对贵重,假如每帧都猜测性地创立这个目标以查看物理是否应该更新,那将是低效的。)
/// {@endtemplate}
///
/// 假如向 [scrollBehavior] 供给了清晰的 [ScrollBehavior],那么该行为供给的 [ScrollPhysics] 将优先于 [physics]。
final ScrollPhysics? physics;

从注释能够了解:

physics特点在 ScrollView 中操控翻滚行为的物理特性,例如翻滚速度、翻滚方向、翻滚是否会反弹等。

  • 默许情况下,physics会依据渠道(iOSAndroid)来挑选适宜的翻滚行为。假如primary特点为false,用户只要在内容满足多,足以翻滚时才干翻滚。假如primarytrue,即便内容缺乏,用户也能够测验翻滚。
  • 假如你想让ScrollView不管内容是否满足,都能够翻滚,你能够设置physicsAlwaysScrollableScrollPhysics。这种情况下,即便primary不是true,翻滚视图也总是能够翻滚。
  • 假如你想让ScrollView严格依照渠道约定进行翻滚,即当内容缺乏时,不管primary的值怎么,都不能翻滚,你能够设置physicsScrollPhysics
  • physics特点能够动态改动,可是只要当你供给的物理目标的类产生改动时,新的物理特点才会收效。这是由于物理目标的创立可能会有必定的开支,假如每一帧都创立新的物理目标来查看是否需求更新物理特点,可能会导致性能问题。
  • 假如你为scrollBehavior供给了一个ScrollBehavior目标,那么这个目标供给的ScrollPhysics会优先于ScrollViewphysics特点。

8. scrollBehavior特点部分

/// {@macro flutter.widgets.shadow.scrollBehavior}
///
/// [ScrollBehavior] 也供给 [ScrollPhysics]。假如在 [physics] 中供给了清晰的 [ScrollPhysics],它将优先,
/// 然后是 [scrollBehavior],然后是承继的先人 [ScrollBehavior]。
final ScrollBehavior? scrollBehavior;

9. shrinkWrap特点部分

/// {@template flutter.widgets.scroll_view.shrinkWrap}
/// 翻滚视图在 [scrollDirection] 中的范围是否应由正在查看的内容确认。
///
/// 假如翻滚视图没有缩短包装,则翻滚视图将扩展到 [scrollDirection] 中答应的最大巨细。
/// 假如翻滚视图在 [scrollDirection] 中的约束是无界的,则 [shrinkWrap] 有必要为 true。
///
/// 缩短包装翻滚视图的内容比扩展到答应的最大巨细要贵重得多,由于内容能够在翻滚过程中扩展和缩短,
/// 这意味着每逢翻滚方位改动时,都需求从头计算翻滚视图的巨细。
///
/// 默许为 false。
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=LUqDNnv_dh0}
/// {@endtemplate}
final bool shrinkWrap;

10. center特点部分

/// [GrowthDirection.forward] 成长方向的第一个子元素。
///
/// [center] 之后的子元素将相关于 [center] 在由 [scrollDirection][reverse] 确认的 [AxisDirection] 中放置。
/// [center] 之前的子元素将相关于 [center] 放置在轴方向的相反方向。这使得 [center] 成为成长方向的拐点。
///
/// [center] 有必要是 [buildSlivers] 构建的滑块之一的键。
///
/// 在 [ScrollView] 的内置子类中,只要 [CustomScrollView] 支持 [center];
/// 关于该类,给定的键有必要是 [CustomScrollView.slivers] 列表中的滑块之一的键。
///
/// 大多数翻滚视图默许按 [GrowthDirection.forward] 排序。
/// 更改 [ScrollView.anchor][ScrollView.center] 或两者的默许值,能够为翻滚视图装备 [GrowthDirection.reverse]。
///
/// {@tool dartpad}
/// 此示例显现了一个 [CustomScrollView],在 [AppBar.bottom] 中有 [Radio] 按钮,
/// 能够改动 [AxisDirection] 来展现不同的装备。[CustomScrollView.anchor][CustomScrollView.center]
/// 特点也被设置为使 0 翻滚偏移坐落视口的中间,[GrowthDirection.forward][GrowthDirection.reverse]
/// 在两边显现。同享 [CustomScrollView.center] 键的滑块坐落 [CustomScrollView.anchor] 的方位。
///
/// ** 拜见 examples/api/lib/rendering/growth_direction/growth_direction.0.dart 中的代码 **
/// {@end-tool}
///
/// 另请拜见:
///
///  * [anchor],它操控 [center] 在视口中的对齐方式。
final Key? center;

11. anchor特点部分

/// {@template flutter.widgets.scroll_view.anchor}
/// 零翻滚偏移的相对方位。
///
/// 例如,假如 [anchor] 是 0.5,由 [scrollDirection] 和 [reverse] 确认的 [AxisDirection] 是 [AxisDirection.down] 或
/// [AxisDirection.up],那么零翻滚偏移在视口中笔直居中。假如 [anchor] 是 1.0,轴方向是 [AxisDirection.right],
/// 那么零翻滚偏移在视口的左边缘。
///
/// 大多数翻滚视图默许按 [GrowthDirection.forward] 排序。
/// 更改 [ScrollView.anchor]、[ScrollView.center] 或两者的默许值,能够为翻滚视图装备 [GrowthDirection.reverse]。
///
/// {@tool dartpad}
/// 此示例显现了一个 [CustomScrollView],在 [AppBar.bottom] 中有 [Radio] 按钮,
/// 能够改动 [AxisDirection] 来展现不同的装备。[CustomScrollView.anchor] 和 [CustomScrollView.center]
/// 特点也被设置为使 0 翻滚偏移坐落视口的中间,[GrowthDirection.forward] 和 [GrowthDirection.reverse]
/// 在两边显现。同享 [CustomScrollView.center] 键的滑块坐落 [CustomScrollView.anchor] 的方位。
///
/// ** 拜见 examples/api/lib/rendering/growth_direction/growth_direction.0.dart 中的代码 **
/// {@end-tool}
/// {@endtemplate}
final double anchor;

12. cacheExtent特点部分

/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
final double? cacheExtent;

13. semanticChildCount特点部分

/// 将供给语义信息的子元素数量。
///
/// [ScrollView] 的一些子类型能够主动揣度此值。例如 [ListView] 将运用子列表中的组件数量,
/// 而 [ListView.separated] 结构函数将运用该数量的一半。
///
/// 关于 [CustomScrollView] 和其他类型,它们不接收构建器或组件列表,有必要清晰供给子计数。假如数量不知道或无限,则应保留未设置或设置为 null。
///
/// 另请拜见:
///
///  * [SemanticsConfiguration.scrollChildCount],对应的语义特点。
final int? semanticChildCount;

14. dragStartBehavior特点部分

/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;

15. keyboardDismissBehavior特点部分

/// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
/// 界说此 [ScrollView] 怎么主动消除键盘的 [ScrollViewKeyboardDismissBehavior]。
/// {@endtemplate}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

16. restorationId特点部分

/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;

17. clipBehavior特点部分

/// {@macro flutter.material.Material.clipBehavior}
///
/// 默许为 [Clip.hardEdge]。
final Clip clipBehavior;

18. getDirection办法部分

/// 回来翻滚视图翻滚的 [AxisDirection]。
///
/// 结合 [scrollDirection][reverse] 布尔值来获取详细的 [AxisDirection]。
///
/// 假如 [scrollDirection][Axis.horizontal],在挑选详细的 [AxisDirection] 时也会考虑环境 [Directionality]。
/// 例如,假如环境 [Directionality][TextDirection.rtl],那么非反向的 [AxisDirection][AxisDirection.left],
/// 反向的 [AxisDirection][AxisDirection.right]@protected
AxisDirection getDirection(BuildContext context) {
  return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
}

19. buildSlivers办法部分

/// 构建放置在视口内的组件列表。
///
/// 子类应重写此办法,以构建视口内部的滑块。
///
/// 要了解更多关于滑块的信息,请拜见 [CustomScrollView.slivers]。
@protected
List<Widget> buildSlivers(BuildContext context);

20. buildViewport办法部分

/// 构建视口(viewport)。
///
/// 子类能够重写此办法来改动视口的构建方式。假如 [shrinkWrap] 为 true,那么默许完成运用 [ShrinkWrappingViewport],
/// 不然运用惯例的 [Viewport]。
///
/// `offset` 参数是从 [Scrollable.viewportBuilder] 获取的值。
///
/// `axisDirection` 参数是从 [getDirection] 获取的值,该值默许运用 [scrollDirection] 和 [reverse]。
///
/// `slivers` 参数是从 [buildSlivers] 获取的值。
@protected
Widget buildViewport(
  BuildContext context,
  ViewportOffset offset,
  AxisDirection axisDirection,
  List<Widget> slivers,
) {
  assert(() {
    switch (axisDirection) {
      case AxisDirection.up:
      case AxisDirection.down:
        return debugCheckHasDirectionality(
          context,
          // 为了确认翻滚视图的交叉轴方向
          why: 'to determine the cross-axis direction of the scroll view',
          // 笔直翻滚视图创立试图从环境 Directionality 确认其交叉轴方向的 Viewport 组件。
          hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction '
                'from the ambient Directionality.',
        );
      case AxisDirection.left:
      case AxisDirection.right:
        return true;
    }
  }());
  if (shrinkWrap) {
    return ShrinkWrappingViewport(
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
      clipBehavior: clipBehavior,
    );
  }
  return Viewport(
    axisDirection: axisDirection,
    offset: offset,
    slivers: slivers,
    cacheExtent: cacheExtent,
    center: center,
    anchor: anchor,
    clipBehavior: clipBehavior,
  );
}

buildViewport 办法用于构建 ScrollView 的视口。

视口是 ScrollView 中可见的部分,它决议了用户在屏幕上看到的内容。视口内的内容能够翻滚,而视口外的内容则不可见。

buildViewport办法接收四个参数:contextoffsetaxisDirectionslivers

参数 描述
context 是当前 BuildContext,它包括了当前 widget 的方位信息和状况
offset 是从 Scrollable.viewportBuilder 获取的值,它表明当前翻滚的方位
axisDirection 是从 getDirection 办法获取的值,它表明翻滚的方向。默许情况下,它运用 scrollDirection 和 reverse 特点来确认
slivers 是从 buildSlivers 办法获取的值,它是一个 Widget 列表,表明视口内的内容

buildViewport办法中:

  • 首要会依据axisDirection的值进行一些断语查看,以确保翻滚视图的交叉轴方向是正确的。
  • 然后,假如shrinkWrap特点为true,则运用ShrinkWrappingViewport来构建视口。ShrinkWrappingViewport是一种特殊的视口,它会依据其子组件的巨细来调整自己的巨细。
  • 假如shrinkWrap特点为false,则运用惯例的Viewport来构建视口。Viewport会尽可能地扩展到最大的可用空间。
  • 最后,不管是ShrinkWrappingViewport还是Viewport,都会运用传入的axisDirectionoffsetslivers参数,以及ScrollViewclipBehaviorcacheExtentcenteranchor特点来进行构建。

21. build办法部分

  @override
  Widget build(BuildContext context) {
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
    final bool effectivePrimary = primary
        ?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection);
    final ScrollController? scrollController = effectivePrimary
        ? PrimaryScrollController.maybeOf(context)
        : controller;
    final Scrollable scrollable = Scrollable(
      dragStartBehavior: dragStartBehavior,
      axisDirection: axisDirection,
      controller: scrollController,
      physics: physics,
      scrollBehavior: scrollBehavior,
      semanticChildCount: semanticChildCount,
      restorationId: restorationId,
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
        return buildViewport(context, offset, axisDirection, slivers);
      },
      clipBehavior: clipBehavior,
    );
    final Widget scrollableResult = effectivePrimary && scrollController != null
        // Further descendant ScrollViews will not inherit the same PrimaryScrollController
        ? PrimaryScrollController.none(child: scrollable)
        : scrollable;
    if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
      return NotificationListener<ScrollUpdateNotification>(
        child: scrollableResult,
        onNotification: (ScrollUpdateNotification notification) {
          final FocusScopeNode focusScope = FocusScope.of(context);
          if (notification.dragDetails != null && focusScope.hasFocus) {
            focusScope.unfocus();
          }
          return false;
        },
      );
    } else {
      return scrollableResult;
    }
  }

ScrollView组件的build办法中:

  • 首要,它调用buildSlivers办法来构建视口内部的组件列表,然后调用getDirection办法来获取翻滚的方向。

  • 接着,它确认是否运用主翻滚操控器。假如primary特点为true,或许没有供给controller而且PrimaryScrollController.shouldInherit回来true,那么effectivePrimary就为true。在这种情况下,翻滚操控器scrollController将运用PrimaryScrollController.maybeOf(context) 获取,不然运用供给的controller

  • 然后,它创立一个Scrollable组件,这个组件包括了翻滚的所有信息,如翻滚方向、翻滚操控器、翻滚物理等。viewportBuilder参数是一个函数,它回来视口组件,这个函数调用buildViewport办法来构建视口。

    假如effectivePrimarytrue而且scrollController不为null,那么它会回来一个PrimaryScrollController.none组件,这样子孙的ScrollView就不会承继同一个PrimaryScrollController。不然,它直接回来Scrollable组件。

  • 最后,假如keyboardDismissBehavior特点设置为ScrollViewKeyboardDismissBehavior.onDrag,那么它会回来一个NotificationListener组件,这个组件会在翻滚更新告诉产生时撤销焦点,然后躲藏键盘。不然,它直接回来Scrollable组件。

22. 其它代码

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  super.debugFillProperties(properties);
  properties.add(EnumProperty<Axis>('scrollDirection', scrollDirection));
  properties.add(FlagProperty('reverse', value: reverse, ifTrue: 'reversed', showName: true));
  properties.add(DiagnosticsProperty<ScrollController>('controller', controller, showName: false, defaultValue: null));
  properties.add(FlagProperty('primary', value: primary, ifTrue: 'using primary controller', showName: true));
  properties.add(DiagnosticsProperty<ScrollPhysics>('physics', physics, showName: false, defaultValue: null));
  properties.add(FlagProperty('shrinkWrap', value: shrinkWrap, ifTrue: 'shrink-wrapping', showName: true));
}

debugFillProperties 办法是 Flutter 结构的一部分,用于在调试时供给有关 ScrollView 的信息。