Flutter笔记:聊一聊Flutter中委托的设计方法
作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
邮箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/134056041
【介绍】Flutter 框架中提供了很多以 “Delegate” 一词结尾的类。Delegate表示中文“委托”,那么这些类为什么以 Delegate 结尾呢?反映了什么思想?本文归纳相关 Delegate 类,并谈一谈其中的设计逻辑。
- 1. 以 GridView 为例,从构造函数说起
- 1.1 默认构造函数的实现
- 1.2 GridView.builder构造函数
- 1.3 GridView.custom构造函数
- 1.4 GridView.count 构造函数
- 1.5 GridView.extent 构造函数
- 2. 什么是委托
- 3. 再看 GridView
- 3.1 回顾
- 3.2 SliverGrid 组件的默认构造函数
- 3.2.2 SliverChild 默认构造函数
- 3.2.2 SliverChildDelegate
- 子元素的生命周期阶段
- (1) SliverChildListDelegate extends [SliverChildDelegate](#SliverChildDelegate)
- (2) SliverChildBuilderDelegate extends [SliverChildDelegate](#SliverChildDelegate)
- 3.2.3 SliverGridDelegate
- 3.3 GridView 的 gridDelegate属性
- 3.4 GridView 的 childrenDelegate属性
- 3.5 GridView 的 buildChildLayout 方法
- 3.6 这就是Flutter设计中的 委托
- 4. 小结与启示
- 附录
GridView({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
super.cacheExtent,
List<Widget> children = const <Widget>[],
int? semanticChildCount,
super.dragStartBehavior,
super.clipBehavior,
super.keyboardDismissBehavior,
super.restorationId,
}) : childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
semanticChildCount: semanticChildCount ?? children.length,
);
从 GridView 默认构造函数的结构可以看出,它本质上不过是创建了一个 SliverChildListDelegate 对象并赋值给 childrenDelegate
。
而 SliverChildListDelegate 是 SliverChildDelegate 的一个实现,它 使用一个固定的子组件列表来生成网格的子组件。这里,children
参数就是这个列表。另外,addAutomaticKeepAlives
、addRepaintBoundaries
和 addSemanticIndexes
参数用于控制子组件的生命周期、是否添加重绘边界和语义索引。
另外一方面,该构造函数种调用了其父类(BoxScrollView)的构造函数。semanticChildCount
参数用于语义分析,它表示 GridView 中的子组件数量。如果 semanticChildCount
为 null
,则使用 children.length
作为默认值。
GridView.builder 构造函数用于创建一个可以滚动的,按需创建的二维部件数组。这对于具有大量(或无限)子部件的网格视图非常合适,因为构建器只会为实际可见的子部件调用。
/// 创建一个可滚动的,按需创建的二维部件数组。
///
/// 对于具有大量(或无限)子部件的网格视图,此构造函数是合适的,因为构建器只会为实际可见的子部件调用。
///
/// 提供非空的 `itemCount` 可以提高 [GridView] 估计最大滚动范围的能力。
///
/// `itemBuilder` 只会被调用大于等于零且小于 `itemCount` 的索引。
///
/// {@macro flutter.widgets.ListView.builder.itemBuilder}
///
/// {@macro flutter.widgets.PageView.findChildIndexCallback}
///
/// [gridDelegate] 参数是必需的。
///
/// `addAutomaticKeepAlives` 参数对应于 [SliverChildBuilderDelegate.addAutomaticKeepAlives] 属性。
/// `addRepaintBoundaries` 参数对应于 [SliverChildBuilderDelegate.addRepaintBoundaries] 属性。
/// `addSemanticIndexes` 参数对应于 [SliverChildBuilderDelegate.addSemanticIndexes] 属性。
GridView.builder({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
required this.gridDelegate,
required NullableIndexedWidgetBuilder itemBuilder,
ChildIndexGetter? findChildIndexCallback,
int? itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
super.cacheExtent,
int? semanticChildCount,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
}) : childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
findChildIndexCallback: findChildIndexCallback,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
semanticChildCount: semanticChildCount ?? itemCount,
);
从代码可以看到,这个构造函数接收多个参数,其中最重要的两个参数是 gridDelegate
和 itemBuilder
。
gridDelegate
是一个 SliverGridDelegate 对象,它决定了网格的布局。这是一个必需的参数。itemBuilder
是一个函数,它接收一个 BuildContext 和一个索引,然后返回一个 Widget。这个函数只会被调用大于等于零且小于itemCount
的索引。这是一个必需的参数。
GridView.builder 构造函数的工作原理是,当需要渲染一个子部件时,它会调用 itemBuilder
函数,传入当前的 BuildContext 和子部件的索引,然后将返回的 组件 添加到网格中。这样,只有当子部件实际需要显示时,才会调用 itemBuilder
函数创建子部件。
此外,GridView.builder 还接收一些其他参数,如 itemCount
、addAutomaticKeepAlives
、addRepaintBoundaries
和 addSemanticIndexes
,这些参数用于控制 GridView 的行为。
最后,GridView.builder 通过 SliverChildBuilderDelegate 创建了一个 childrenDelegate
,然后传递给 GridView 的父类构造函数。这个 childrenDelegate
决定了如何为 GridView 创建子部件。
/// 使用自定义 [SliverGridDelegate] 和自定义 [SliverChildDelegate] 创建一个可滚动的二维部件数组。
///
/// 要使用 [IndexedWidgetBuilder] 回调来构建子部件,可以使用 [SliverChildBuilderDelegate] 或使用 [GridView.builder] 构造函数。
///
/// [gridDelegate] 和 [childrenDelegate] 参数不能为空。
const GridView.custom({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
required this.gridDelegate,
required this.childrenDelegate,
super.cacheExtent,
super.semanticChildCount,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
});
GridView.custom 构造函数用于创建一个可滚动的二维部件数组,它允许你完全自定义 **SliverGridDelegate **和 SliverChildDelegate。
- SliverGridDelegate 决定了网格的布局,例如每行的列数、每个子部件的尺寸等。
- SliverChildDelegate 决定了如何生成网格的子部件。你可以使用 SliverChildBuilderDelegate 来按需生成子部件,或者使用 SliverChildListDelegate 来生成一个固定列表的子部件。
GridView.custom 构造函数接收多个参数,其中最重要的两个参数是 gridDelegate 和 childrenDelegate,这两个参数都是必需的。
gridDelegate
是一个 SliverGridDelegate 对象,它决定了网格的布局。childrenDelegate
是一个 SliverChildDelegate 对象,它决定了如何为 GridView 创建子部件。
GridView.custom 会根据
gridDelegate
的设置来布局网格,然后调用childrenDelegate
来生成子部件。这样,你可以完全自定义 GridView 的布局和子部件的生成方式。
在这个构造函数的实现种:
gridDelegate
实现网格的布局工作:gridDelegate
是 SliverGridDelegate 类型的对象,它是一个委托,负责定义网格的布局。具体来说,它决定了网格中每行的列数,以及每个格子的大小。当 GridView 需要布局其子部件时,它会调用gridDelegate
的方法来获取布局信息。所以,你可以说 gridDelegate 委托了网格的布局工作。childrenDelegate
实现子部件的创建工作:childrenDelegate
是 SliverChildDelegate 类型的对象,它是一个委托,负责创建网格的子部件。具体来说,当 GridView 需要渲染一个新的子部件时,它会调用childrenDelegate
的方法来创建这个子部件。
GridView.count 构造函数用于创建一个可滚动的二维部件数组,其中交叉轴上有固定数量的格子。这个构造函数接收多个参数,其中最重要的是
crossAxisCount
,它决定了交叉轴上的格子数量。此外,还可以设置 mainAxisSpacing 和 crossAxisSpacing 来控制格子之间的间距,以及 childAspectRatio 来控制每个格子的宽高比。
该构造函数的代码为:
/// 创建一个可滚动的,二维部件数组,交叉轴上有固定数量的格子。
///
/// 使用 [SliverGridDelegateWithFixedCrossAxisCount] 作为 [gridDelegate]。
///
/// `addAutomaticKeepAlives` 参数对应于 [SliverChildListDelegate.addAutomaticKeepAlives] 属性。
/// `addRepaintBoundaries` 参数对应于 [SliverChildListDelegate.addRepaintBoundaries] 属性。两者都不能为空。
///
/// 另请参阅:
///
/// * [SliverGrid.count],[SliverGrid] 的等效构造函数。
GridView.count({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
required int crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
super.cacheExtent,
List<Widget> children = const <Widget>[],
int? semanticChildCount,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
}) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
),
childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
semanticChildCount: semanticChildCount ?? children.length,
);
从代码可以看出,GridView.count 构造函数会根据 crossAxisCount
、mainAxisSpacing
、crossAxisSpacing
和 childAspectRatio
的值来布局网格,然后根据 children
列表来创建子部件。这使得你可以轻松地创建一个具有固定列数的网格视图。
在 GridView.count 构造函数中,gridDelegate
被设置为 SliverGridDelegateWithFixedCrossAxisCount 对象。这个对象会根据 crossAxisCount
、mainAxisSpacing
、crossAxisSpacing
和 childAspectRatio
的值来布局网格。
childrenDelegate
被设置为 SliverChildListDelegate 对象,它会根据传入的 children
列表来创建子部件。addAutomaticKeepAlives
、addRepaintBoundaries
和 addSemanticIndexes
参数会传递给 SliverChildListDelegate,用于控制子部件的生命周期、是否添加重绘边界和语义索引。
GridView.extent 构造函数用于创建一个可滚动的二维部件数组,其中交叉轴上的每个格子都有最大的宽度。
这个构造函数接收多个参数,其中最重要的是 maxCrossAxisExtent,它决定了交叉轴上每个格子的最大宽度。此外,还可以设置 mainAxisSpacing 和 crossAxisSpacing 来控制格子之间的间距,以及 childAspectRatio 来控制每个格子的宽高比。
该构造函数源码为:
/// 创建一个可滚动的,二维部件数组,每个格子在交叉轴上都有最大的范围。
///
/// 使用 [SliverGridDelegateWithMaxCrossAxisExtent] 作为 [gridDelegate]。
///
/// `addAutomaticKeepAlives` 参数对应于 [SliverChildListDelegate.addAutomaticKeepAlives] 属性。
/// `addRepaintBoundaries` 参数对应于 [SliverChildListDelegate.addRepaintBoundaries] 属性。两者都不能为空。
///
/// 另请参阅:
///
/// * [SliverGrid.extent],[SliverGrid] 的等效构造函数。
GridView.extent({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
required double maxCrossAxisExtent,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
super.cacheExtent,
List<Widget> children = const <Widget>[],
int? semanticChildCount,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
}) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
),
childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
semanticChildCount: semanticChildCount ?? children.length,
);
GridView.extent 构造函数会根据 maxCrossAxisExtent
、mainAxisSpacing
、crossAxisSpacing
和 childAspectRatio
的值来布局网格,然后根据 children
列表来创建子部件。这使得你可以轻松地创建一个具有固定最大宽度的网格视图。
在 GridView.extent 构造函数中,gridDelegate
被设置为 SliverGridDelegateWithMaxCrossAxisExtent 对象。这个对象会根据 maxCrossAxisExtent
、mainAxisSpacing
、crossAxisSpacing
和 childAspectRatio
的值来布局网格。
childrenDelegate
被设置为 SliverChildListDelegate 对象,它会根据传入的 children
列表来创建子部件。addAutomaticKeepAlives
、addRepaintBoundaries
和 addSemanticIndexes
参数会传递给 SliverChildListDelegate,用于控制子部件的生命周期、是否添加重绘边界和语义索引。
Flutter中,“Delegate” 结尾的类通常表示这些类的主要目的是"委托"(Delegate)某些工作或责任给其他类或者提供了一种扩展或自定义的机制。 —— 这个命名约定的目的是提醒开发者这些类通常用于 委托某种功能 或 提供可插拔性。
具体来说,以下是一些常见情况,“Delegate” 结尾的类可能委托了什么:
业务逻辑委托:某些 “Delegate” 类可能用于将特定的业务逻辑委托给其他类。例如,一个
ListView.builder
中的itemBuilder
参数允许你委托生成列表项的逻辑给开发者自定义的函数。路由过渡委托:一些类用于定义页面路由之间的过渡效果,比如
PageRouteBuilder
,允许开发者委托定义过渡效果的工作。渲染和布局委托:在渲染和布局阶段,一些 “Delegate” 类允许你自定义渲染对象的外观和布局,除了这里介绍的 GridView使用两类 Delegate 分别实现提供子组件和子组件布局外——再比如
SliverPersistentHeaderDelegate
用于定义 SliverAppBar 的外观和行为。插件或扩展机制:一些 “Delegate” 类可能用于实现插件或扩展的接口,这允许开发者自定义、扩展或替换某些功能的实现。
策略模式的委托:某些 “Delegate” 类实际上是策略模式的实现,允许你根据需要在不同的算法或行为之间切换,而不需要修改客户端代码。
“Delegate” 结尾的类通常表示它们起到了一种"桥梁"或"中介"的作用,将某些具体的实现委托给其他类,以实现灵活性、可定制性和可扩展性。这种命名约定有助于在代码中识别和理解这些类的用途和作用。
Flutter中以 “Delegate” 结尾的这些类通常,以分离不同部分之间的关注点并提供更好的可维护性和可扩展性。这些 “Delegate” 类通常遵循某些规则和设计原则,如以下所示:
分离关注点:“Delegate” 类有助于将代码拆分为不同的部分,每个部分关注特定的责任。这有助于减少代码的复杂性,提高可读性和可维护性。
单一责任原则(SRP):“Delegate” 类通常遵循 SRP,每个类关注一个单一的责任。这使得代码更容易测试、理解和维护。
策略模式:在许多情况下, “Delegate” 类实现策略模式,允许您根据需要在不同的算法或行为之间切换。这种设计模式有助于动态地选择不同的实现,而不需要修改客户端代码。
插件机制:许多 Delegate 类用于实现插件或扩展的接口,以便轻松添加和扩展功能。这样,开发人员可以创建自定义实现以满足其需求,而不必修改核心代码。
这些 Delegate 类的共同特点是它们提供了灵活性、可扩展性和可定制性,使得 Flutter· 应用程序可以更容易地满足不同的需求。通过使用这些类,开发人员可以更轻松地实现一些通用的设计原则,例如单一责任原则和策略模式,以改进应用程序的结构和性能。
本文 第1小节 介绍了 GridView 的用法。 GridView 有多种构造函数,其中一些构造函数使用了委托(Delegate)的概念,例如 GridView.builder 和 GridView.custom,现在以这两个构造函数简单回顾一下。
- GridView.builder:这个构造函数允许你动态地创建网格项目。它需要一个 GridDelegate 和一个 IndexedWidgetBuilder。GridDelegate 用于控制网格的布局,IndexedWidgetBuilder 用于生成网格项目。其中:
GridDelegate是一个抽象类,它有多个实现,例如:SliverGridDelegateWithFixedCrossAxisCount 和 SliverGridDelegateWithMaxCrossAxisExtent。这些实现类允许你自定义网格的布局,例如设置交叉轴的项目数量或最大尺寸。
IndexedWidgetBuilder 是一个回调函数,它接收一个上下文和一个索引,返回一个新的组件。你可以在这个回调函数中根据索引生成不同的组件。
- GridView.
custom
:这个构造函数允许你使用自定义的 SliverChildDelegate 来生成网格项目。其中:- SliverChildDelegate 是一个抽象类,它有多个实现,例如SliverChildBuilderDelegate 和 SliverChildListDelegate。这些实现类允许你自定义网格项目的生成方式,例如使用一个回调函数或一个固定的小组件列表。
在这两个构造函数中,GridView 组件都使用了 Delegate 的类来提供灵活性和可扩展性。
通过使用这些类,实现定义网格的布局和项目的生成方式,而不需要修改 GridView 组件的源代码。
这应该算是一种的 策略模式 的应用,它 允许你在不同的策略之间动态地切换,以满足不同的需求。
进一步看 GridView 的代码,还需要在先介绍 GridView 的 gridDelegate 属性 和 childrenDelegate 属性以后,最后再看负责构建 GridView 的子布局的 buildChildLayout
方法。
const SliverGrid({
super.key,
required super.delegate,
required this.gridDelegate,
});
delegate
:这是一个必需参数,类型为 SliverChildDelegate。它负责 提供和构建子组件。
而其中 SliverChildDelegate 包括:- SliverChildListDelegate(用于处理 固定数量 的子组件)
- SliverChildBuilderDelegate(用于处理懒加载的子组件)。
gridDelegate
:这也是一个必需参数,类型为 SliverGridDelegate。它负责控制 SliverGrid 的布局,例如每行的列数,子部件的长宽比,子部件之间的间距等。常见的 SliverGridDelegate 包括:
- SliverGridDelegateWithFixedCrossAxisCount(固定列数)
- SliverGridDelegateWithMaxCrossAxisExtent(最大交叉轴范围)。
SliverChildDelegate 是为
slivers
提供子组件的 抽象类。
许多 slivers
会懒加载它们的 box 子组件,以避免创建比视口可见的子组件更多。它们不是通过显式的 List 接收子组件,而是通过 SliverChildDelegate 接收子组件。
【比较】
SliverChildBuilderDelegate 是一个使用 构建器回调 来构造子组件的委托
SliverChildListDelegate 是一个具有显式子列表的委托。
子元素的生命周期包括 创建(Creation)、销毁(Destruction) 和 销毁缓和(Destruction mitigation)三个阶段:
在 创建(Creation) 阶段,可见子组件的元素、状态和渲染对象将基于现有组件。
如 SliverChildListDelegate 的情况 或 懒加载提供的组件(如 SliverChildBuilderDelegate 的情况)懒加载创建。
在 销毁阶段,当一个子组件滚动出视图时,关联的元素子树、状态和渲染对象被销毁。
当它滚动回来时,一个新的子组件在 sliver
的同一位置将懒加载重新创建,同时新的元素、状态和渲染对象也会被创建。
在销毁缓和(Destruction mitigation) 阶段,为了在子元素滚动进出视图时 保留状态,有以下可能的选项:
- 将非琐碎的驱动 UI 状态的业务逻辑的所有权移出
sliver
子组件子树; - 让 KeepAlive 成为需要保留的
sliver
子组件子树的根组件; - 使用 AutomaticKeepAlive 组件。
如果在单个滚动视图中使用多个委托,每个委托的第一个子组件总是会被布局,即使它超出了当前可视区域。这是因为至少需要一个子组件来为整个滚动视图 estimateMaxScrollOffset
,因为它使用当前构建的子组件来估计剩余子组件的范围。
SliverChildListDelegate 是一个用于为 slivers
提供子组件的 委托Delegate,它使用一个明确的列表来生成子组件。这种方式很方便,但如果子组件的数量很大,或者子组件的创建成本很高,使用 SliverChildBuilderDelegate 或直接继承 SliverChildDelegate 来实现懒加载可能会更有效率。
SliverChildBuilderDelegate 是一个用于为 slivers 提供子组件的委托Delegate,它使用一个构建器回调来按需生成子组件。这种方式允许子组件进行懒加载,即只有当子组件真正需要显示时才会创建。这对于子组件数量大或者创建成本高的情况非常有用,因为它可以帮助我们避免不必要的资源浪费。此外,SliverChildBuilderDelegate 还提供了一些额外的控制,如是否为每个子组件添加自动保活(AutomaticKeepAlive)、重绘边界(RepaintBoundary)以及语义索引(SemanticIndex)。
SliverGridDelegateWithFixedCrossAxisCount 是一个用于为 slivers
提供网格布局的委托Delegate,它在交叉轴上具有固定数量的单元格。
=> 这意味着无论网格的总体大小如何,交叉轴上的单元格数量始终保持不变。这对于需要创建均匀分布的网格布局(例如图片网格)非常有用。此外,你还可以控制单元格之间的间距(mainAxisSpacing
和 crossAxisSpacing
)、单元格的宽高比(childAspectRatio
)以及网格的内边距(padding
)。
SliverGridDelegateWithMaxCrossAxisExtent 是一个用于为 slivers
提供网格布局的委托Delegate,它在交叉轴上的单元格具有最大扩展尺寸。
=> 这意味着单元格的尺寸会自动调整以适应网格的总体大小,但不会超过设定的最大尺寸。这对于需要创建响应式布局的网格非常有用,因为你可以根据屏幕的大小自动调整单元格的数量。此外,你还可以控制单元格之间的间距(mainAxisSpacing
和 crossAxisSpacing
)、单元格的宽高比(childAspectRatio
)以及网格的内边距(padding
)。
gridDelegate 是 GridView 类中的一个属性,它的类型是 SliverGridDelegate。这个属性是一个委托(delegate),它决定了 GridView 中子部件的布局。
其源代码为:
/// 一个委托,控制 [GridView] 中子部件的布局。
///
/// [GridView],[GridView.builder] 和 [GridView.custom] 构造函数允许你明确指定这个委托。其他构造函数隐式创建一个 [gridDelegate]。
final SliverGridDelegate gridDelegate;
gridDelegate
属性的作用就是定义 GridView 中子部件的布局。这使得 GridView 可以灵活地适应各种需求,例如创建固定列数的网格,或者创建具有固定最大宽度的网格。
SliverGridDelegate 是一个抽象类,它有两个常用的子类:SliverGridDelegateWithFixedCrossAxisCount 和 SliverGridDelegateWithMaxCrossAxisExtent。
- SliverGridDelegateWithFixedCrossAxisCount 创建一个网格,其中交叉轴上有固定数量的格子。你可以指定交叉轴上的格子数量,以及格子之间的间距和宽高比。
- SliverGridDelegateWithMaxCrossAxisExtent 创建一个网格,其中交叉轴上的每个格子都有最大的宽度。你可以指定每个格子的最大宽度,以及格子之间的间距和宽高比。
在 GridView、GridView.builder 和 GridView.custom 构造函数中,你可以明确指定 gridDelegate
。在其他构造函数中,gridDelegate
会自动创建。
/// 一个委托,为 [GridView] 提供子部件。
///
/// [GridView.custom] 构造函数允许你明确指定这个委托。其他构造函数创建一个包装给定子部件列表的 [childrenDelegate]。
final SliverChildDelegate childrenDelegate;
可以看到,childrenDelegate
属性类型为 SliverChildDelegate。这个属性是一个 委托(delegate),它决定了如何为 GridView 创建子部件。
SliverChildDelegate 是一个抽象类,它有两个常用的子类:SliverChildListDelegate 和 SliverChildBuilderDelegate。其中:
- SliverChildListDelegate 接收一个固定长度的子部件列表,然后按照列表顺序创建子部件。
- SliverChildBuilderDelegate 接收一个构建函数,然后按需创建子部件。这对于具有大量子部件的 GridView 非常有用,因为只有当子部件实际需要显示时,才会调用构建函数创建子部件。
在 GridView.custom 构造函数中,你可以明确指定 childrenDelegate
。在其他构造函数中,childrenDelegate
会自动创建,通常是包装给定的子部件列表。
因此,childrenDelegate 属性的作用就是定义如何为 GridView 创建子部件。这使得 GridView 可以灵活地适应各种需求,例如创建固定数量的子部件,或者按需创建子部件。
buildChildLayout
负责构建 GridView 的子布局。
buildChildLayout 方法是 GridView 的核心,它负责构建 GridView 的子布局。这个方法的实现非常简单,只是创建了一个 SliverGrid 对象,并将 childrenDelegate 和 gridDelegate 传递给了它。它的代码如下:
@override
Widget buildChildLayout(BuildContext context) {
return SliverGrid(
delegate: childrenDelegate, // SliverChildDelegate 对象,它决定了如何创建和布局子项。
gridDelegate: gridDelegate, // SliverGridDelegate 对象,它决定了网格的布局。
);
}
可以看到,负责构建 GridView 的子布局的 实际上是通过 SliverGrid实现的,其中需要指定两个信息:
- 如何创建和布局子项:delegate参数;
SliverGrid 的delegate
参数类型为 SliverChildDelegate,它负责提供和构建子部件。childrenDelegate 是传递给 delegate 的参数,它可能是 SliverChildListDelegate(用于处理固定数量的子部件)或 SliverChildBuilderDelegate(用于处理懒加载的子部件)。 - 网格的布局:gridDelegate参数。
在GridView的 buildChildLayout
方法中,我们可以看到委托(Delegate)的强大之处。
GridView 本身并没有实现如何创建和布局子项,以及如何布局网格,而是将这些工作委托给了 childrenDelegate 和 gridDelegate。
childrenDelegate
是一个 SliverChildDelegate 对象,它决定了如何创建和布局子项。这个委托可以是 SliverChildListDelegate(用于处理固定数量的子部件)或 SliverChildBuilderDelegate(用于处理懒加载的子部件)。通过这个委托,GridView 可以灵活地创建和布局子项,无论是固定数量的子部件,还是懒加载的子部件。
gridDelegate
是一个 SliverGridDelegate 对象,它决定了网格的布局。这个委托可以是 SliverGridDelegateWithFixedCrossAxisCount(固定列数的网格)或 SliverGridDelegateWithMaxCrossAxisExtent(最大交叉轴范围的网格)。通过这个委托,GridView 可以灵活地布局网格,无论是固定列数的网格,还是最大交叉轴范围的网格。
总的来说,通过委托(Delegate)的方式,GridView 将创建和布局子项,以及布局网格的工作交给了其他对象,从而实现了代码的解耦和复用。
在 Flutter 中,委托(Delegate)类 可以认为是一种 设计模式,它允许一个类将某些任务或责任委托给另一个类。这种设计模式可以提高代码的 灵活性 和 可复用性,因为它允许我们在不修改原有类的情况下,改变或扩展某些功能。
在 GridView 的例子中,我们看到了两种类型的委托:SliverChildDelegate 和 SliverGridDelegate。SliverChildDelegate 负责创建和布局子项,而 SliverGridDelegate 负责布局网格。通过这两个委托,GridView 可以灵活地创建和布局子项,以及布局网格,而无需自己实现这些功能。
这种设计模式的优点是:
分离关注点:委托允许我们将不同的功能分离到不同的类中,每个类只关注一个特定的任务或责任。这有助于减少代码的复杂性,提高可读性和可维护性。
提高复用性:通过委托,我们可以在不同的上下文中复用同一个类。例如,我们可以在不同的 GridView 中复用同一个 SliverChildDelegate 或 SliverGridDelegate。
提高灵活性:通过委托,我们可以在运行时改变某些功能的实现。例如,我们可以在运行时改变 GridView 的 gridDelegate,从而改变网格的布局。
这种模式可以应用于以后的编程中。例如,假设我们正在设计一个自定义的列表组件 MyListView,我们可以设计两个委托:ItemBuilder 和 ItemLayoutDelegate。ItemBuilder 负责创建列表项,ItemLayoutDelegate 负责布局列表项。
class MyListView {
final ItemBuilder itemBuilder;
final ItemLayoutDelegate itemLayoutDelegate;
MyListView({required this.itemBuilder, required this.itemLayoutDelegate});
// ...
}
typedef ItemBuilder = Widget Function(BuildContext context, int index);
abstract class ItemLayoutDelegate {
double getItemSize(int index);
// ...
}
通过这种方式,我们可以在不修改 CustomListView 的情况下,改变列表项的创建方式和布局方式,只需要提供不同的 ItemBuilder 和 ItemLayoutDelegate 即可。这就是委托(Delegate)设计模式的强大之处。
这种委托(Delegate)编程模式在 Flutter 中得到了广泛的应用。通过理解和掌握这种设计模式,我们可以更好地理解和使用 Flutter,以及编写出更灵活、更可复用的代码。
以下是类名和它们的描述的表格形式:
类名 | 描述 |
---|---|
DefaultPlatformMenuDelegate |
用于创建默认平台菜单的委托,定制菜单外观和行为。 |
DesktopTextSelectionToolbarLayoutDelegate |
自定义桌面端文本选择工具栏布局和外观的委托。 |
DiagnosticsSerializationDelegate |
支持诊断信息的序列化和反序列化,用于查看应用程序状态和结构。 |
FlowDelegate |
定制Flow布局的委托,用于排列子组件。 |
InspectorSerializationDelegate |
支持Flutter开发工具中检查器信息的序列化和反序列化。 |
ListWheelChildBuilderDelegate |
构建滚动选择器(ListWheelScrollView)的子项的委托。 |
ListWheelChildListDelegate |
传递子项列表给滚动选择器的委托。 |
ListWheelChildLoopingListDelegate |
支持无限循环滚动选择器的委托。 |
LocalizationsDelegate |
本地化国际化支持的委托,切换不同本地化语言。 |
MultiChildLayoutDelegate |
自定义多子组件布局的委托,与MultiChildLayout 组件一起使用。 |
MultiSelectableSelectionContainerDelegate |
自定义多选择容器的委托,支持选择多个子组件。 |
PlatformMenuDelegate |
创建平台菜单的委托,帮助创建一致的菜单。 |
RouterDelegate |
导航路由的委托,管理Flutter应用的导航栈和路由。 |
ScrollActivityDelegate |
自定义滚动活动的委托,处理滚动活动事件。 |
ScrollDragController |
控制拖拽滚动的委托,控制滚动速度和行为。 |
SearchDelegate |
创建搜索界面和处理搜索操作的委托,与搜索组件一起使用。 |
SelectionContainer |
支持文本选择和复制操作的容器,允许选择和复制文本。 |
SemanticsConfiguration |
定义语义信息的配置,帮助无障碍用户理解和操作应用程序。 |
SemanticsGestureDelegate |
自定义语义手势的委托,支持无障碍用户的交互。 |
ShadowRoot |
表示DOM元素的Shadow DOM根,通常与Web视图集成一起使用。 |
ShadowRootExtension |
扩展Shadow DOM的委托,支持自定义Shadow DOM的行为。 |
SingleChildLayoutDelegate |
自定义单子组件布局的委托,与SingleChildLayout 组件一起使用。 |
SingleSelectableSelectionContainerDelegate |
支持单一选择的容器的委托,与单一选择容器一起使用。 |
SliverChildBuilderDelegate |
构建Sliver子组件的委托,通常与Sliver列表一起使用。 |
SliverChildListDelegate |
将Sliver子组件作为列表传递给Sliver列表的委托。 |
SliverFillViewport |
填充Sliver视口的委托,在Sliver列表中的某些情况下使用。 |
SliverGridDelegate |
定义Sliver网格布局的委托,通常与Sliver网格列表一起使用。 |
SliverGridDelegateWithFixedCrossAxisCount |
定义具有固定交叉轴方向子项数量的Sliver网格布局的委托。 |
SliverGridDelegateWithMaxCrossAxisExtent |
定义具有最大交叉轴方向子项尺寸的Sliver网格布局的委托。 |
SliverMultiBoxAdaptorWidget |
在Sliver列表中包装多个子组件的组件。 |
SliverPersistentHeader |
创建Sliver列表中的持久性标题的组件。 |
SliverPersistentHeaderDelegate |
定义Sliver列表中持久性标题的委托。 |
SpellCheckSuggestionsToolbarLayoutDelegate |
自定义拼写检查建议工具栏的布局和外观的委托。 |
TestDefaultBinaryMessenger |
用于测试目的的默认二进制消息传递器,通常不用于生产环境。 |
TextSelectionGestureDetectorBuilderDelegate |
构建文本选择手势检测器的委托,通常与文本选择操作相关。 |
TextSelectionToolbarLayoutDelegate |
自定义文本选择工具栏的布局和外观的委托。 |
TransitionDelegate |
自定义页面路由转场效果的委托,创建自定义的页面切换动画。 |
TwoDimensionalChildBuilderDelegate |
构建二维滚动视图的子组件的委托。 |
TwoDimensionalChildDelegate |
二维滚动视图的子组件委托,支持定制子组件的位置和行为。 |
TwoDimensionalChildListDelegate |
在二维滚动视图中传递子项列表的委托。 |
TwoDimensionalScrollView |
用于支持二维滚动的滚动视图的委托。 |
ZoneDelegate |
用于Zone委托,通常用于资源管理和隔离。 |
/// 为 slivers 提供子组件的委托。
///
/// 许多 slivers 会懒加载它们的 box 子组件,以避免创建比视口可见的子组件更多。
/// 它们不是通过显式的 [List] 接收子组件,而是通过 [SliverChildDelegate] 接收子组件。
///
/// 子类化 [SliverChildDelegate] 是不常见的。相反,考虑使用已有的子类,
/// 它们提供了适配器到构建器回调或显式子列表。
///
/// {@template flutter.widgets.SliverChildDelegate.lifecycle}
/// ## 子元素的生命周期
///
/// ### 创建
///
/// 在布局列表时,可见子组件的元素、状态和渲染对象将基于现有组件(如 [SliverChildListDelegate] 的情况)
/// 或懒加载提供的组件(如 [SliverChildBuilderDelegate] 的情况)懒加载创建。
///
/// ### 销毁
///
/// 当一个子组件滚动出视图时,关联的元素子树、状态和渲染对象被销毁。
/// 当它滚动回来时,一个新的子组件在 sliver 的同一位置将懒加载重新创建,
/// 同时新的元素、状态和渲染对象也会被创建。
///
/// ### 减轻销毁
///
/// 为了在子元素滚动进出视图时保留状态,有以下可能的选项:
///
/// * 将非琐碎的驱动 UI 状态的业务逻辑的所有权移出 sliver 子组件子树。
/// 例如,如果列表包含帖子,它们的点赞数来自缓存的网络响应,
/// 将帖子列表和点赞数存储在列表外的数据模型中。让 sliver 子组件 UI 子树
/// 能够从真实源模型对象轻松重新创建。在子组件子树中使用 [StatefulWidget]
/// 仅存储瞬时 UI 状态。
///
/// * 让 [KeepAlive] 成为需要保留的 sliver 子组件子树的根组件。
/// [KeepAlive] 组件将子组件子树的顶部渲染对象子组件标记为 keepalive。
/// 当关联的顶部渲染对象滚动出视图时,sliver 将子组件的渲染对象(以及相应的元素和状态)
/// 保留在缓存列表中,而不是销毁它们。当滚动回视图时,渲染对象会按原样重绘
/// (如果在此期间没有被标记为 dirty)。
///
/// 这只有在 [SliverChildDelegate] 的子类不通过 `addAutomaticKeepAlives` 和
/// `addRepaintBoundaries` 将其他组件(如 [AutomaticKeepAlive] 和 [RepaintBoundary])包装到
/// 子组件子树中时才有效。
///
/// * 使用 [AutomaticKeepAlive] 组件(默认插入到 [SliverChildListDelegate] 或 [SliverChildListDelegate] 中)。
/// [AutomaticKeepAlive] 允许后代组件控制子树是否真的保持活动状态。这种行为与
/// [KeepAlive] 形成对比,后者将无条件地保持子树活动状态。
///
/// 例如,[EditableText] 组件在其文本字段有输入焦点时,会发出信号让其 sliver
/// 组件在其文本字段有输入焦点时,会发出信号让其 sliver 子元素子树保持活动状态。
/// 如果它没有焦点,且没有其他后代通过 [KeepAliveNotification] 发出保持活动的信号,
/// 当滚动离开视图时,sliver 子元素子树将被销毁。
///
/// [AutomaticKeepAlive] 的后代通常通过使用 [AutomaticKeepAliveClientMixin],
/// 然后实现 [AutomaticKeepAliveClientMixin.wantKeepAlive] getter 和调用
/// [AutomaticKeepAliveClientMixin.updateKeepAlive] 来发出保持活动的信号。
///
/// ## 在 [Viewport] 中使用多个委托
///
/// 如果在单个滚动视图中使用多个委托,每个委托的第一个子组件总是会被布局,
/// 即使它超出了当前可视区域。这是因为至少需要一个子组件来为整个滚动视图
/// [estimateMaxScrollOffset],因为它使用当前构建的子组件来估计剩余子组件的范围。
/// {@endtemplate}
///
/// 另请参阅:
///
/// * [SliverChildBuilderDelegate],这是一个使用构建器回调来构造子组件的委托。
/// * [SliverChildListDelegate],这是一个具有显式子列表的委托。
abstract class SliverChildDelegate {
/// 抽象的 const 构造函数。这个构造函数使子类能够提供 const 构造函数,
/// 以便它们可以在 const 表达式中使用。
const SliverChildDelegate();
/// 返回给定索引的子组件。
///
/// 如果被要求构建一个大于存在的索引的组件,应返回 null。如果这返回 null,
/// [estimatedChildCount] 必须随后返回一个精确的非 null 值
/// (然后用于实现 [RenderSliverBoxChildManager.childCount])。
///
/// 子类通常覆盖此函数,并将其子组件包装在 [AutomaticKeepAlive]、[IndexedSemantics] 和
/// [RepaintBoundary] 组件中。
///
/// 此方法返回的值被缓存。要表示组件已更改,必须提供一个新的委托,
/// 并且新委托的 [shouldRebuild] 方法必须返回 true。
Widget? build(BuildContext context, int index);
/// 返回此委托将构建的子组件数量的估计值。
///
/// 用于估计最大滚动偏移量,如果 [estimateMaxScrollOffset] 返回 null。
///
/// 如果有无限多的子组件,或者估计子组件数量太困难,返回 null。
///
/// 一旦 [build] 返回 null,这必须返回一个精确的数字,
/// 因为它用于实现 [RenderSliverBoxChildManager.childCount]。
int? get estimatedChildCount => null;
/// 返回所有子组件的最大滚动范围的估计值。
///
/// 如果子类有关于其最大滚动范围的额外信息,应覆盖此函数。
///
/// 默认实现返回 null,这会导致调用者从给定参数中推断最大滚动偏移量。
double? estimateMaxScrollOffset(
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) => null;
/// 在布局结束时调用,表示现在布局已完成。
///
/// `firstIndex` 参数是在当前布局中包含的第一个子组件的索引。
/// `lastIndex` 参数是在当前布局中包含的最后一个子组件的索引。
///
/// 对于希望跟踪哪些子组件包含在底层渲染树中的子类很有用。
void didFinishLayout(int firstIndex, int lastIndex) { }
/// 每当向 sliver 提供子委托类的新实例时调用。
///
/// 如果新实例代表的信息与旧实例不同,则该方法应返回 true,否则应返回 false。
///
/// 如果该方法返回 false,则可能会优化掉 [build] 调用。
bool shouldRebuild(covariant SliverChildDelegate oldDelegate);
/// 查找与关联键的子元素的索引。
///
/// 在 [SliverMultiBoxAdaptorElement] 的 `performRebuild` 中调用,
/// 以检查子组件是否移动到了不同的位置。它应返回与关联键的子元素的索引,如果未找到,返回 null。
///
/// 如果未提供,当从子组件构建器返回的子组件顺序改变时,
/// 子组件可能不会映射到其现有的 [RenderObject]。这可能导致状态丢失。
int? findIndexByKey(Key key) => null;
@override
String toString() {
final List<String> description = <String>[];
debugFillDescription(description);
return '${describeIdentity(this)}(${description.join(", ")})';
}
/// 为 [toString] 使用,向给定的描述中添加额外的信息。
@protected
@mustCallSuper
void debugFillDescription(List<String> description) {
try {
final int? children = estimatedChildCount;
if (children != null) {
description.add('estimated child count: $children');
}
} catch (e) {
// 异常被转发到组件检查器。
description.add('estimated child count: EXCEPTION (${e.runtimeType})');
}
}
}
/// 使用明确列表为 slivers 提供子组件的委托。
///
/// 许多 slivers 会懒加载它们的 box 子组件,以避免创建比视口可见的子组件更多。
/// 这个委托使用一个明确的列表来提供子组件,这种方式很方便,但减少了懒加载子组件的优势。
///
/// 通常来说,预先构建所有的组件并不高效。更好的方式是使用 [SliverChildBuilderDelegate]
/// 或直接继承 [SliverChildDelegate] 来创建一个可以按需构建组件的委托。
///
/// 这个类适用于以下情况:子组件列表在很早之前就已知(理想情况下,子组件本身就是编译时常量),
/// 因此每次创建委托时都不需要构建子组件,或者列表很小,很可能始终可见(因此没有必要按需构建)。
/// 例如,对话框的主体可能符合这两种情况。
///
/// 如果 [addAutomaticKeepAlives] 为 true(默认值),则给定 [children] 列表中的组件会自动包装在
/// [AutomaticKeepAlive] 组件中。如果 [addRepaintBoundaries] 为 true(也是默认值),则会包装在
/// [RepaintBoundary] 组件中。
///
/// ## 辅助功能
///
/// [CustomScrollView] 要求其语义子组件使用 [IndexedSemantics] 进行注释。
/// 默认情况下,委托会将 `addSemanticIndexes` 参数设置为 true 来完成这项工作。
///
/// 如果在单个滚动视图中使用了多个委托,那么默认情况下索引将不正确。
/// 可以使用 `semanticIndexOffset` 来偏移每个委托的语义索引,使索引单调递增。
/// 例如,如果滚动视图包含两个委托,第一个委托有 10 个子组件贡献语义,那么第二个委托应该将其子组件偏移 10。
///
/// 在某些情况下,只有子组件的一个子集应该用语义索引进行注释。
/// 例如,在 [ListView.separated()] 中,分隔符没有与之关联的索引。
/// 这是通过提供一个 `semanticIndexCallback` 来完成的,它为分隔符索引返回 null,
/// 并将非分隔符索引向下取整到一半。
///
/// 参见 [SliverChildBuilderDelegate],了解使用 `semanticIndexOffset` 和 `semanticIndexCallback` 的示例代码。
///
/// 另请参见:
///
/// * [SliverChildBuilderDelegate],这是一个使用构建器回调来构建子组件的委托。
class SliverChildListDelegate extends SliverChildDelegate {
/// 使用给定列表为 slivers 提供子组件的委托。
///
/// [children]、[addAutomaticKeepAlives]、[addRepaintBoundaries]、
/// [addSemanticIndexes] 和 [semanticIndexCallback] 参数必须非 null。
///
/// 如果子组件的顺序永远不会改变,考虑使用常量 [SliverChildListDelegate.fixed] 构造函数。
SliverChildListDelegate(
this.children, {
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
this.semanticIndexOffset = 0,
}) : _keyToIndex = <Key?, int>{null: 0};
/// 创建一个使用给定列表为 slivers 提供子组件的委托的常量版本。
///
/// 如果子组件的顺序会改变,考虑使用常规 [SliverChildListDelegate] 构造函数。
///
/// [children]、[addAutomaticKeepAlives]、[addRepaintBoundaries]、
/// [addSemanticIndexes] 和 [semanticIndexCallback] 参数必须非 null。
const SliverChildListDelegate.fixed(
this.children, {
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
this.semanticIndexOffset = 0,
}) : _keyToIndex = null;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives}
final bool addAutomaticKeepAlives;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries}
final bool addRepaintBoundaries;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.addSemanticIndexes}
final bool addSemanticIndexes;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.semanticIndexOffset}
final int semanticIndexOffset;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.semanticIndexCallback}
final SemanticIndexCallback semanticIndexCallback;
/// 要显示的组件。
///
/// 如果这个列表将要被改变,通常最好在每个子组件上放一个 [Key],
/// 这样框架就可以匹配旧配置和新配置,并维护底层的渲染对象。
///
/// 另外,在 Flutter 中,[Widget] 是不可变的,所以直接修改 [children],
/// 例如 `someWidget.children.add(...)` 或将原始列表值的引用传递给 [children] 参数,
/// 将导致行为不正确。每次修改子组件列表时,都必须提供一个新的列表对象。
///
/// 下面的代码纠正了上述问题。
///
/// ```dart
/// class SomeWidgetState extends State<SomeWidget> {
/// final List<Widget> _children = <Widget>[];
///
/// void someHandler() {
/// setState(() {
/// // 这里的 key 允许 Flutter 重用底层的渲染对象,即使子组件列表被重新创建。
/// _children.add(ChildWidget(key: UniqueKey()));
/// });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// // 由于 Widget 是不可变的,所以总是创建一个新的子组件列表。
/// return PageView(children: List<Widget>.of(_children));
/// }
/// }
/// ```
final List<Widget> children;
/// 用于缓存子组件的 key 到 index 查找的映射。
///
/// 在 [_findChildIndex] 的懒加载过程中,_keyToIndex[null] 被用作当前索引。
/// _keyToIndex 不应该用于查找 null key。
final Map<Key?, int>? _keyToIndex;
bool get _isConstantInstance => _keyToIndex == null;
int? _findChildIndex(Key key) {
if (_isConstantInstance) {
return null;
}
// 懒加载填充 [_keyToIndex]。
if (!_keyToIndex!.containsKey(key)) {
int index = _keyToIndex![null]!;
while (index < children.length) {
final Widget child = children[index];
if (child.key != null) {
_keyToIndex![child.key] = index;
}
if (child.key == key) {
// 记录当前索引以供下次函数调用。
_keyToIndex![null] = index + 1;
return index;
}
index += 1;
}
_keyToIndex![null] = index;
} else {
return _keyToIndex![key];
}
return null;
}
@override
int? findIndexByKey(Key key) {
final Key childKey;
if (key is _SaltedValueKey) {
final _SaltedValueKey saltedValueKey = key;
childKey = saltedValueKey.value;
} else {
childKey = key;
}
return _findChildIndex(childKey);
}
@override
Widget? build(BuildContext context, int index) {
if (index < 0 || index >= children.length) {
return null;
}
Widget child = children[index];
final Key? key = child.key != null? _SaltedValueKey(child.key!) : null;
if (addRepaintBoundaries) {
child = RepaintBoundary(child: child);
}
if (addSemanticIndexes) {
final int? semanticIndex = semanticIndexCallback(child, index);
if (semanticIndex != null) {
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
}
}
if (addAutomaticKeepAlives) {
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
}
return KeyedSubtree(key: key, child: child);
}
@override
int? get estimatedChildCount => children.length;
@override
bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
return children != oldDelegate.children;
}
}
/// 使用构建器回调为 slivers 提供子组件的委托。
///
/// 许多 slivers 会懒加载它们的 box 子组件,以避免创建比视口可见的子组件更多。
/// 这个委托使用 [NullableIndexedWidgetBuilder] 回调提供子组件,
/// 因此子组件甚至不需要在显示之前就构建。
///
/// 如果 [addAutomaticKeepAlives] 为 true(默认值),则构建器回调返回的组件会自动包装在
/// [AutomaticKeepAlive] 组件中。如果 [addRepaintBoundaries] 为 true(也是默认值),
/// 则会包装在 [RepaintBoundary] 组件中。
///
/// ## 辅助功能
///
/// [CustomScrollView] 要求其语义子组件使用 [IndexedSemantics] 进行注释。
/// 在默认的委托中,当 `addSemanticIndexes` 参数设置为 true 时,会自动完成此操作。
///
/// 如果在单个滚动视图中使用多个委托,则默认情况下索引将不正确。
/// `semanticIndexOffset` 可用于偏移每个委托的语义索引,使索引单调递增。
/// 例如,如果滚动视图包含两个委托,其中第一个有 10 个贡献语义的子组件,
/// 则第二个委托应将其子组件偏移 10。
///
/// {@tool snippet}
///
/// 此示例代码展示了如何使用 `semanticIndexOffset` 处理单个滚动视图中的多个委托。
///
/// ```dart
/// CustomScrollView(
/// semanticChildCount: 4,
/// slivers: <Widget>[
/// SliverGrid(
/// gridDelegate: _gridDelegate,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return const Text('...');
/// },
/// childCount: 2,
/// ),
/// ),
/// SliverGrid(
/// gridDelegate: _gridDelegate,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return const Text('...');
/// },
/// childCount: 2,
/// semanticIndexOffset: 2,
/// ),
/// ),
/// ],
/// )
/// ```
/// {@end-tool}
///
/// 在某些情况下,只有子组件的子集应使用语义索引进行注释。
/// 例如,在 [ListView.separated()] 中,分隔符没有与之关联的索引。
/// 这是通过提供一个 `semanticIndexCallback` 来完成的,它对分隔符索引返回 null,
/// 并将非分隔符索引减半。
///
/// {@tool snippet}
///
/// 此示例代码展示了如何使用 `semanticIndexCallback` 处理使用语义索引注释子节点的子集。
/// 在奇数索引处有一个 [Spacer] 组件,它不应该有语义索引。
///
/// ```dart
/// CustomScrollView(
/// semanticChildCount: 5,
/// slivers: <Widget>[
/// SliverGrid(
/// gridDelegate: _gridDelegate,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// if (index.isEven) {
/// return const Text('...');
/// }
/// return const Spacer();
/// },
/// semanticIndexCallback: (Widget widget, int localIndex) {
/// if (localIndex.isEven) {
/// return localIndex ~/ 2;
/// }
/// return null;
/// },
/// childCount: 10,
/// ),
/// ),
/// ],
/// )
/// ```
/// {@end-tool}
///
/// 另请参阅:
///
/// * [SliverChildListDelegate],这是一个具有显式子列表的委托。
/// * [IndexedSemantics],这是手动使用语义索引注释子节点的示例。
class SliverChildBuilderDelegate extends SliverChildDelegate {
/// 使用给定的构建器回调创建一个为 slivers 提供子组件的委托。
///
/// [builder]、[addAutomaticKeepAlives]、[addRepaintBoundaries]、
/// [addSemanticIndexes] 和 [semanticIndexCallback] 参数必须非 null。
///
/// 如果 [builder] 返回子组件的顺序有变化,考虑提供一个 [findChildIndexCallback]。
/// 这允许委托找到之前位于不同索引的子组件的新索引,以便将现有状态附加到新位置的 [Widget] 上。
const SliverChildBuilderDelegate(
this.builder, {
this.findChildIndexCallback,
this.childCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
this.semanticIndexOffset = 0,
});
/// 用于为 sliver 构建子组件的回调。
///
/// 只会为大于或等于零且小于 [childCount](如果 [childCount] 非空)的索引调用。
///
/// 如果被要求构建一个索引大于存在的索引的组件,应返回 null。
///
/// 如果 [childCount] 为 null 并且 [builder] 总是提供零大小的组件(如 `Container()`
/// 或 `SizedBox.shrink()`),可能会导致无限循环或内存耗尽。如果可能,提供非零大小的子组件,
/// 从 [builder] 返回 null,或设置 [childCount]。
///
/// 委托会将此构建器返回的子组件包装在 [RepaintBoundary] 组件中。
final NullableIndexedWidgetBuilder builder;
/// 此委托可以提供的子组件总数。
///
/// 如果为 null,子组件的数量由 [builder] 返回 null 的最小索引确定。
///
/// 如果 [childCount] 为 null 并且 [builder] 总是提供零大小的组件(如 `Container()`
/// 或 `SizedBox.shrink()`),可能会导致无限循环或内存耗尽。如果可能,提供非零大小的子组件,
/// 从 [builder] 返回 null,或设置 [childCount]。
final int? childCount;
/// {@template flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives}
/// 是否将每个子组件包装在 [AutomaticKeepAlive] 中。
///
/// 通常,懒加载列表中的子组件会被包装在 [AutomaticKeepAlive] 组件中,
/// 以便子组件可以使用 [KeepAliveNotification] 在它们否则会被滚动出屏幕时保留其状态。
///
/// 如果子组件将手动维护其 [KeepAlive] 状态,必须禁用此功能(和 [addRepaintBoundaries])。
/// 如果提前知道没有任何子组件会尝试保持自己活动,禁用此功能可能更有效。
///
/// 默认为 true。
/// {@endtemplate}
///
/// Defaults to true.
/// {@endtemplate}
final bool addAutomaticKeepAlives;
/// {@template flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries}
/// 是否将每个子组件包装在 [RepaintBoundary] 中。
///
/// 通常,滚动容器中的子组件被包装在重绘边界中,以便在列表滚动时不需要重绘。
/// 如果子组件易于重绘(例如,纯色块或短文本片段),则不添加重绘边界并始终在滚动期间重绘子组件可能更有效。
///
/// 默认为 true。
/// {@endtemplate}
final bool addRepaintBoundaries;
/// {@template flutter.widgets.SliverChildBuilderDelegate.addSemanticIndexes}
/// 是否将每个子组件包装在 [IndexedSemantics] 中。
///
/// 通常,滚动容器中的子组件必须使用语义索引进行注释,以生成正确的辅助功能通知。
/// 只有当索引已由 [IndexedSemantics] 组件提供时,才应将此设置为 false。
///
/// 默认为 true。
///
/// 另请参阅:
///
/// * [IndexedSemantics],了解如何手动提供语义索引的说明。
/// {@endtemplate}
final bool addSemanticIndexes;
/// {@template flutter.widgets.SliverChildBuilderDelegate.semanticIndexOffset}
/// 添加到此组件生成的语义索引的初始偏移量。
///
/// 默认为零。
/// {@endtemplate}
final int semanticIndexOffset;
/// {@template flutter.widgets.SliverChildBuilderDelegate.semanticIndexCallback}
/// 当 [addSemanticIndexes] 为 true 时使用的 [SemanticIndexCallback]。
///
/// 默认为为每个组件提供索引。
/// {@endtemplate}
final SemanticIndexCallback semanticIndexCallback;
/// {@template flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback}
/// 在重新排序时,根据其键找到子组件的新索引的回调。
///
/// 如果未提供,当从子组件构建器返回的子组件顺序改变时,
/// 子组件可能不会映射到其现有的 [RenderObject]。这可能导致状态丢失。
///
/// 此回调应接受一个输入 [Key],并返回与该键关联的子元素的索引,如果未找到则返回 null。
/// {@endtemplate}
final ChildIndexGetter? findChildIndexCallback;
@override
int? findIndexByKey(Key key) {
if (findChildIndexCallback == null) {
return null;
}
final Key childKey;
if (key is _SaltedValueKey) {
final _SaltedValueKey saltedValueKey = key;
childKey = saltedValueKey.value;
} else {
childKey = key;
}
return findChildIndexCallback!(childKey);
}
@override
@pragma('vm:notify-debugger-on-exception')
Widget? build(BuildContext context, int index) {
if (index < 0 || (childCount != null && index >= childCount!)) {
return null;
}
Widget? child;
try {
child = builder(context, index);
} catch (exception, stackTrace) {
child = _createErrorWidget(exception, stackTrace);
}
if (child == null) {
return null;
}
final Key? key = child.key != null ? _SaltedValueKey(child.key!) : null;
if (addRepaintBoundaries) {
child = RepaintBoundary(child: child);
}
if (addSemanticIndexes) {
final int? semanticIndex = semanticIndexCallback(child, index);
if (semanticIndex != null) {
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
}
}
if (addAutomaticKeepAlives) {
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
}
return KeyedSubtree(key: key, child: child);
}
@override
int? get estimatedChildCount => childCount;
@override
bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true;
}
- 点赞
- 收藏
- 关注作者
评论(0)