Flutter.源码分析flutter/packages/flutter/lib/src/widg...ScrollView

举报
jcLee95 发表于 2023/11/13 20:55:53 2023/11/13
【摘要】 本文提供 Flutter 框架中 ScrollView 类源码注释的中文翻译以及必要的分析解说。Flutter.源码分析 flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ScrollView
Flutter.源码分析
ScrollView
flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ScrollView

本文提供 Flutter 框架中 ScrollView 类源码注释的中文翻译以及必要的分析解说。




/// 一个组合了 [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 {

  /// 创建一个可以滚动的组件。
  ///
  /// 如果没有提供 [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);

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

  /// {@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;

/// {@template flutter.widgets.scroll_view.controller}
/// 可用于控制滚动视图滚动到哪个位置的对象。
///
/// 如果 [primary] 为 true,则必须为 null。
///
/// [ScrollController] 有多个用途。它可以用来控制初始滚动位置(参见 [ScrollController.initialScrollOffset])。
/// 它可以用来控制滚动视图是否应自动在 [PageStorage] 中保存和恢复其滚动位置(参见 [ScrollController.keepScrollOffset])。
/// 它可以用来读取当前滚动位置(参见 [ScrollController.offset]),或改变它(参见 [ScrollController.animateTo])。
/// {@endtemplate}
final ScrollController? controller;

/// {@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。


/// {@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 会根据平台(iOS  Android)来选择合适的滚动行为。如果 primary 属性为 false,用户只有在内容足够多,足以滚动时才能滚动。如果 primary  true,即使内容不足,用户也可以尝试滚动。

  • 如果你想让 ScrollView 无论内容是否足够,都可以滚动,你可以设置 physics  AlwaysScrollableScrollPhysics。这种情况下,即使 primary 不是 true,滚动视图也总是可以滚动。

  • 如果你想让 ScrollView 严格按照平台约定进行滚动,即当内容不足时,无论 primary 的值如何,都不能滚动,你可以设置 physics  ScrollPhysics

  • physics 属性可以动态改变,但是只有当你提供的物理对象的类发生改变时,新的物理属性才会生效。这是因为物理对象的创建可能会有一定的开销,如果每一帧都创建新的物理对象来检查是否需要更新物理属性,可能会导致性能问题。

  • 如果你为 scrollBehavior 提供了一个 ScrollBehavior 对象,那么这个对象提供的 ScrollPhysics 会优先于 ScrollView  physics 属性。


/// {@macro flutter.widgets.shadow.scrollBehavior}
///
/// [ScrollBehavior] 也提供 [ScrollPhysics]。如果在 [physics] 中提供了明确的 [ScrollPhysics],它将优先,
/// 然后是 [scrollBehavior],然后是继承的祖先 [ScrollBehavior]。
final ScrollBehavior? scrollBehavior;

/// {@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;

/// [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;

/// {@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;

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

/// 将提供语义信息的子元素数量。
///
/// [ScrollView] 的一些子类型可以自动推断此值。例如 [ListView] 将使用子列表中的组件数量,
/// 而 [ListView.separated] 构造函数将使用该数量的一半。
///
/// 对于 [CustomScrollView] 和其他类型,它们不接收构建器或组件列表,必须明确提供子计数。如果数量未知或无限,则应保留未设置或设置为 null。
///
/// 另请参见:
///
///  * [SemanticsConfiguration.scrollChildCount],对应的语义属性。
final int? semanticChildCount;

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

/// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
/// 定义此 [ScrollView] 如何自动消除键盘的 [ScrollViewKeyboardDismissBehavior]。
/// {@endtemplate}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

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

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

/// 返回滚动视图滚动的 [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);
}

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

/// 构建视口(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 方法接收四个参数:contextoffsetaxisDirection  slivers

参数 描述
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,都会使用传入的 axisDirectionoffset  slivers 参数,以及 ScrollView  clipBehaviorcacheExtentcenter  anchor 属性来进行构建。


  @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 方法来构建视口。

    如果 effectivePrimary  true 并且 scrollController 不为 null,那么它会返回一个 PrimaryScrollController.none 组件,这样后代的 ScrollView 就不会继承同一个 PrimaryScrollController。否则,它直接返回 Scrollable 组件。

  • 最后,如果 keyboardDismissBehavior 属性设置为 ScrollViewKeyboardDismissBehavior.onDrag,那么它会返回一个 NotificationListener 组件,这个组件会在滚动更新通知发生时取消焦点,从而隐藏键盘。否则,它直接返回 Scrollable 组件。


@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 的信息。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。