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. 类注释部分
- 2. 构造方法部分
- 3. scrollDirection 属性部分
- 4. reverse 属性部分
- 5. controller 属性部分
- 6. primary属性部分
- 7. physics属性部分
- 8. scrollBehavior属性部分
- 9. shrinkWrap属性部分
- 10. center属性部分
- 11. anchor属性部分
- 12. cacheExtent属性部分
- 13. semanticChildCount属性部分
- 14. dragStartBehavior属性部分
- 15. keyboardDismissBehavior属性部分
- 16. restorationId属性部分
- 17. clipBehavior属性部分
- 18. getDirection方法部分
- 19. buildSlivers方法部分
- 20. buildViewport方法部分
- 21. build方法部分
- 22. 其它代码
1. 类注释部分
scala
/// 一个组合了 [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. 构造方法部分
kotlin
/// 创建一个可以滚动的组件。
///
/// 如果没有提供 [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 属性部分
ruby
/// {@template flutter.widgets.scroll_view.scrollDirection}
/// 滚动视图偏移量增加的 [Axis]。
///
/// 对于可能发生活动滚动的方向,请参见 [ScrollDirection]。
///
/// 默认为 [Axis.vertical]。
/// {@endtemplate}
final Axis scrollDirection;
4. reverse 属性部分
ruby
/// {@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 属性部分
ruby
/// {@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属性部分
ruby
/// {@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属性部分
ruby
/// {@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
属性。
8. scrollBehavior属性部分
ini
/// {@macro flutter.widgets.shadow.scrollBehavior}
///
/// [ScrollBehavior] 也提供 [ScrollPhysics]。如果在 [physics] 中提供了明确的 [ScrollPhysics],它将优先,
/// 然后是 [scrollBehavior],然后是继承的祖先 [ScrollBehavior]。
final ScrollBehavior? scrollBehavior;
9. shrinkWrap属性部分
ruby
/// {@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属性部分
ini
/// [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属性部分
perl
/// {@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属性部分
arduino
/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
final double? cacheExtent;
13. semanticChildCount属性部分
arduino
/// 将提供语义信息的子元素数量。
///
/// [ScrollView] 的一些子类型可以自动推断此值。例如 [ListView] 将使用子列表中的组件数量,
/// 而 [ListView.separated] 构造函数将使用该数量的一半。
///
/// 对于 [CustomScrollView] 和其他类型,它们不接收构建器或组件列表,必须明确提供子计数。如果数量未知或无限,则应保留未设置或设置为 null。
///
/// 另请参见:
///
/// * [SemanticsConfiguration.scrollChildCount],对应的语义属性。
final int? semanticChildCount;
14. dragStartBehavior属性部分
arduino
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
15. keyboardDismissBehavior属性部分
ruby
/// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
/// 定义此 [ScrollView] 如何自动消除键盘的 [ScrollViewKeyboardDismissBehavior]。
/// {@endtemplate}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
16. restorationId属性部分
arduino
/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;
17. clipBehavior属性部分
arduino
/// {@macro flutter.material.Material.clipBehavior}
///
/// 默认为 [Clip.hardEdge]。
final Clip clipBehavior;
18. getDirection方法部分
css
/// 返回滚动视图滚动的 [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方法部分
less
/// 构建放置在视口内的组件列表。
///
/// 子类应重写此方法,以构建视口内部的滑块。
///
/// 要了解更多关于滑块的信息,请参见 [CustomScrollView.slivers]。
@protected
List<Widget> buildSlivers(BuildContext context);
20. buildViewport方法部分
php
/// 构建视口(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
方法接收四个参数:context
、offset
、axisDirection
和 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 ,都会使用传入的
axisDirection
、offset
和slivers
参数,以及 ScrollView 的clipBehavior
、cacheExtent
、center
和anchor
属性来进行构建。
21. build方法部分
ini
@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 组件。
22. 其它代码
php
@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 的信息。