Flutter源码分析.flutter/packages/flutter/lib/src/widgets/...ListView
【摘要】 本文提供 Flutter 框架中 ListView 类源码注释的中文翻译以及必要的分析解说。
本文提供 Flutter 框架中 ListView 类源码注释的中文翻译以及必要的分析解说。
/// 一个线性排列的可滚动组件列表。
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=KJpkjHGiI5A}
///
/// [ListView] 是最常用的滚动组件。它在滚动方向上一个接一个地显示其子组件。在交叉轴上,
/// 子组件需要填充 [ListView]。
///
/// 如果非空,[itemExtent] 会强制子组件在滚动方向上具有给定的尺寸。
///
/// 如果非空,[prototypeItem] 会强制子组件在滚动方向上具有与给定组件相同的尺寸。
///
/// 指定 [itemExtent] 或 [prototypeItem] 比让子组件确定自己的尺寸更高效,因为滚动机制可以利用
/// 子组件尺寸的预知来节省工作,例如当滚动位置发生剧变时。
///
/// 你不能同时指定 [itemExtent] 和 [prototypeItem],只能指定其中一个或都不指定。
///
/// 构造 [ListView] 有四种选项:
///
/// 1. 默认构造函数接受一个明确的 [List<Widget>] 子组件。这个构造函数适用于具有少量子组件的列表视图,
/// 因为构造 [List] 需要为可能在列表视图中显示的每个子组件做工作,而不仅仅是那些实际可见的子组件。
///
/// 2. [ListView.builder] 构造函数接受一个 [IndexedWidgetBuilder],它根据需求构建子组件。这个构造函数适用于
/// 具有大量(或无限)子组件的列表视图,因为构建器只为那些实际可见的子组件调用。
///
/// 3. [ListView.separated] 构造函数接受两个 [IndexedWidgetBuilder]:itemBuilder 根据需求构建子项,
/// separatorBuilder 类似地构建出现在子项之间的分隔符子项。这个构造函数适用于具有固定数量子组件的列表视图。
///
/// 4. [ListView.custom] 构造函数接受一个 [SliverChildDelegate],它提供了自定义子模型的其他方面的能力。例如,
/// [SliverChildDelegate] 可以控制用于估计实际不可见的子组件的大小的算法。
///
/// 要控制滚动视图的初始滚动偏移量,提供一个设置了其 [ScrollController.initialScrollOffset] 属性的 [controller]。
///
/// 默认情况下,[ListView] 会自动填充列表的可滚动极限,以避免 [MediaQuery] 的填充指示的部分阻塞。要避免此行为,
/// 使用零 [padding] 属性覆盖。
///
/// {@tool snippet}
// 这个示例使用 [ListView] 的默认构造函数,它接受一个明确的 [List<Widget>] 子组件。这个 [ListView] 的子组件由
/// 带有 [Text] 的 [Container] 组成。
///
/// ![一个包含3个琥珀色容器和示例文本的 ListView。](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view.png)
///
/// ```dart
/// ListView(
/// padding: const EdgeInsets.all(8),
/// children: <Widget>[
/// Container(
/// height: 50,
/// color: Colors.amber[600],
/// child: const Center(child: Text('Entry A')),
/// ),
/// Container(
/// height: 50,
/// color: Colors.amber[500],
/// child: const Center(child: Text('Entry B')),
/// ),
/// Container(
/// height: 50,
/// color: Colors.amber[100],
/// child: const Center(child: Text('Entry C')),
/// ),
/// ],
/// )
/// ```
/// {@end-tool}
///
/// {@tool snippet}
/// 这个示例与前一个相同,使用 [ListView.builder] 构造函数创建相同的列表。使用 [IndexedWidgetBuilder],子组件可以懒加载,数量可以无限。
///
/// ![一个包含3个琥珀色容器和示例文本的 ListView。](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_builder.png)
///
/// ```dart
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
/// Widget build(BuildContext context) {
/// return ListView.builder(
/// padding: const EdgeInsets.all(8),
/// itemCount: entries.length,
/// itemBuilder: (BuildContext context, int index) {
/// return Container(
/// height: 50,
/// color: Colors.amber[colorCodes[index]],
/// child: Center(child: Text('Entry ${entries[index]}')),
/// );
/// }
/// );
/// }
/// ```
/// {@end-tool}
///
/// {@tool snippet}
/// 这个示例继续从前面的示例构建,使用 [ListView.separated] 创建类似的列表。这里,[Divider] 被用作分隔符。
///
/// ![一个包含3个琥珀色容器和示例文本以及它们之间的 Divider 的 ListView。](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_separated.png)
///
/// ```dart
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
/// Widget build(BuildContext context) {
/// return ListView.separated(
/// padding: const EdgeInsets.all(8),
/// itemCount: entries.length,
/// itemBuilder: (BuildContext context, int index) {
/// return Container(
/// height: 50,
/// color: Colors.amber[colorCodes[index]],
/// child: Center(child: Text('Entry ${entries[index]}')),
/// );
/// },
/// separatorBuilder: (BuildContext context, int index) => const Divider(),
/// );
/// }
/// ```
/// {@end-tool}
///
/// ## 子元素的生命周期
///
/// ### 创建
///
/// 在布局列表时,可见子元素的元素、状态和渲染对象将根据现有组件(例如使用默认构造函数时)或懒加载提供的组件(例如使用 [ListView.builder] 构造函数时)懒加载创建。
///
/// ### 销毁
///
/// 当一个子组件滚动出视图时,关联的元素子树、状态和渲染对象被销毁。当它滚动回来时,将懒加载创建一个新的子组件,以及新的元素、状态和渲染对象。
///
/// ### 减轻销毁
///
/// 为了在子元素滚动进出视图时保留状态,有以下可能的选项:
///
/// * M将非琐碎的驱动 UI 状态的业务逻辑的所有权移出列表子组件子树。例如,如果列表包含帖子,其点赞数来自缓存的网络响应,将帖子列表和点赞数存储在列表外的数据模型中。让列表子组件 UI 子树可以轻松地从真实源模型对象重新创建。在子组件子树中使用 [StatefulWidget] 来存储瞬时 UI 状态。
///
/// * 让 [KeepAlive] 成为需要保留的列表子组件子树的根组件。[KeepAlive] 组件将子树的顶部渲染对象子项标记为 keepalive。当关联的顶部渲染对象滚动出视图时,列表将子组件的渲染对象(以及相应的元素和状态)保存在缓存列表中,而不是销毁它们。当滚动回视图时,渲染对象会原样重绘(如果在此期间没有被标记为脏)。
///
/// 这只有在 `addAutomaticKeepAlives` 和 `addRepaintBoundaries` 为 false 时才有效,因为这些参数会导致 [ListView] 用其他组件包装每个子组件子树。
///
/// * 使用 [AutomaticKeepAlive] 组件(当 `addAutomaticKeepAlives` 为 true 时默认插入)。[AutomaticKeepAlive] 允许后代组件控制子树是否真的保持活动状态。
/// 这种行为与 [KeepAlive] 形成对比,后者会无条件地保持子树活动(alive)。
///
/// 例如,[EditableText] 组件在其文本字段具有输入焦点时,会发出信号让其列表子元素子树保持活动。如果它没有焦点,且没有其他后代通过 [KeepAliveNotification] 发出保持活动的信号,
/// 列表子元素子树将在滚动出视图时被销毁。
///
/// [AutomaticKeepAlive] 的后代通常通过使用 [AutomaticKeepAliveClientMixin] 发出保持活动的信号,然后实现 [AutomaticKeepAliveClientMixin.wantKeepAlive] getter
/// 并调用 [AutomaticKeepAliveClientMixin.updateKeepAlive]。
///
/// ## 转换为 [CustomScrollView]
///
/// [ListView] 基本上是一个 [CustomScrollView],其 [CustomScrollView.slivers] 属性中有一个 [SliverList]。
///
/// 如果 [ListView] 不再足够,例如因为滚动视图既要有列表又要有网格,或者因为列表要与 [SliverAppBar] 结合等,那么直接使用 [CustomScrollView] 替换 [ListView] 的代码是直接的。
///
/// [ListView] 上的 [key]、[scrollDirection]、[reverse]、[controller]、[primary]、[physics] 和 [shrinkWrap] 属性直接映射到 [CustomScrollView] 上的同名属性。
///
/// [CustomScrollView.slivers] 属性应该是一个列表,包含以下内容:
/// * 如果 [itemExtent] 和 [prototypeItem] 都为 null,则为 [SliverList];
/// * 如果 [itemExtent] 不为 null,则为 [SliverFixedExtentList];
/// * 如果 [prototypeItem] 不为 null,则为 [SliverPrototypeExtentList]。
///
/// [ListView] 上的 [childrenDelegate] 属性对应于 [SliverList.delegate](或 [SliverFixedExtentList.delegate])属性。[ListView] 构造函数的 `children` 参数对应于
/// [childrenDelegate] 是一个带有相同参数的 [SliverChildListDelegate]。[ListView.builder] 构造函数的 `itemBuilder` 和 `itemCount` 参数对应于
/// [childrenDelegate] 是一个带有等效参数的 [SliverChildBuilderDelegate]。
///
/// [padding] 属性对应于在 [CustomScrollView.slivers] 属性中有一个 [SliverPadding],而不是列表本身,并且 [SliverList] 是 [SliverPadding] 的子组件。
///
/// [CustomScrollView] 不会像 [ListView] 那样自动避免 [MediaQuery] 的阻塞。要复制这种行为,将 slivers 包装在 [SliverSafeArea] 中。
///
/// 一旦代码已经被移植为使用 [CustomScrollView],其他的 slivers,如 [SliverGrid] 或 [SliverAppBar],可以放入 [CustomScrollView.slivers] 列表中。
///
/// {@tool snippet}
///
/// 这里有两个简短的片段,显示了一个 [ListView] 及其使用 [CustomScrollView] 的等效代码:
///
/// ```dart
/// ListView(
/// padding: const EdgeInsets.all(20.0),
/// children: const <Widget>[
/// Text("I'm dedicating every day to you"),
/// Text('Domestic life was never quite my style'),
/// Text('When you smile, you knock me out, I fall apart'),
/// Text('And I thought I was so smart'),
/// ],
/// )
/// ```
/// {@end-tool}
/// {@tool snippet}
///
/// ```dart
/// CustomScrollView(
/// slivers: <Widget>[
/// SliverPadding(
/// padding: const EdgeInsets.all(20.0),
/// sliver: SliverList(
/// delegate: SliverChildListDelegate(
/// <Widget>[
/// const Text("I'm dedicating every day to you"),
/// const Text('Domestic life was never quite my style'),
/// const Text('When you smile, you knock me out, I fall apart'),
/// const Text('And I thought I was so smart'),
/// ],
/// ),
/// ),
/// ),
/// ],
/// )
/// ```
/// {@end-tool}
///
/// ## 空列表的特殊处理
///
/// 一个常见的设计模式是为空列表有一个自定义的 UI。在 Flutter 中实现这个的最好方式就是在构建时根据条件替换 [ListView],显示你需要的空列表状态的组件:
///
/// {@tool snippet}
///
/// 简单的空列表界面示例:
///
/// ```dart
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(title: const Text('Empty List Test')),
/// body: itemCount > 0
/// ? ListView.builder(
/// itemCount: itemCount,
/// itemBuilder: (BuildContext context, int index) {
/// return ListTile(
/// title: Text('Item ${index + 1}'),
/// );
/// },
/// )
/// : const Center(child: Text('No items')),
/// );
/// }
/// ```
/// {@end-tool}
///
/// ## 空列表的特殊处理
///
/// 一个常见的设计模式是为空列表有一个自定义的 UI。在 Flutter 中实现这个的最好方式就是在构建时根据条件替换 [ListView],显示你需要的空列表状态的组件:
///
/// {@tool dartpad}
/// 这个示例展示了在 [ListView] 或 [GridView] 中 [ListTile] 选择的自定义实现。
/// 长按任何 [ListTile] 以启用选择模式。
///
/// **参见 examples/api/lib/widgets/scroll_view/list_view.0.dart 中的代码**
/// {@end-tool}
///
/// {@macro flutter.widgets.BoxScroll.scrollBehaviour}
///
/// 另请参阅:
///
/// * [SingleChildScrollView],这是一个有单个子组件的可滚动组件。
/// * [PageView],这是一个滚动的子组件列表,每个子组件都是视口(viewport)的大小。
/// * [GridView],这是一个可滚动的,2D 组件数组。
/// * [CustomScrollView],这是一个使用 slivers 创建自定义滚动效果的可滚动组件。
/// * [ListBody],它以类似的方式排列其子组件,但没有滚动。
/// * [ScrollNotification] 和 [NotificationListener],可以用来观察滚动位置,而无需使用 [ScrollController]。
/// * [布局组件目录](https://flutter.dev/widgets/layout/)。
/// * Cookbook: [使用列表](https://flutter.dev/docs/cookbook/lists/basic-list)
/// * Cookbook: [处理长列表](https://flutter.dev/docs/cookbook/lists/long-lists)
/// * Cookbook: [创建水平列表](https://flutter.dev/docs/cookbook/lists/horizontal-list)
/// * Cookbook: [创建包含不同类型项目的列表](https://flutter.dev/docs/cookbook/lists/mixed-list)
/// * Cookbook: [实现滑动以解除](https://flutter.dev/docs/cookbook/gestures/dismissible)
class ListView extends BoxScrollView{
// ...
}
/// 从显式的 [List] 创建一个可滚动的线性组件数组。
///
/// 当列表视图的子组件数量较少时,此构造函数是合适的,因为构造 [List] 需要为可能在列表视图中显示的每个子组件做工作,
/// 而不仅仅是那些实际可见的子组件。
///
/// 与框架中的其他组件一样,此组件期望 [children] 列表在此处传入后不会发生变化。
/// 有关更多详细信息,请参阅 [SliverChildListDelegate.children] 的文档。
///
/// 通常,使用 [ListView.builder] 按需创建子组件更高效,因为它会在必要时懒加载组件子组件。
///
/// `addAutomaticKeepAlives` 参数对应于 [SliverChildListDelegate.addAutomaticKeepAlives] 属性。
/// `addRepaintBoundaries` 参数对应于 [SliverChildListDelegate.addRepaintBoundaries] 属性。
/// `addSemanticIndexes` 参数对应于 [SliverChildListDelegate.addSemanticIndexes] 属性。这些参数都不能为 null。
ListView({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
this.itemExtent,
this.prototypeItem,
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,
}) : assert(
itemExtent == null || prototypeItem == null,
// 你只能传入 itemExtent 或 prototypeItem,不能两者都传。
'You can only pass itemExtent or prototypeItem, not both.',
),
childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
semanticChildCount: semanticChildCount ?? children.length,
);
/// 创建一个可滚动的线性组件数组,这些组件是按需创建的。
///
/// 对于具有大量(或无限)子组件的列表视图,此构造函数是合适的,因为构建器只会为实际可见的子组件调用。
///
/// 提供非空的 `itemCount` 可以提高 [ListView] 估计最大滚动范围的能力。
///
/// `itemBuilder` 回调只会被调用大于等于零且小于 `itemCount` 的索引。
///
/// {@template flutter.widgets.ListView.builder.itemBuilder}
/// `itemBuilder` 返回 `null` 是合法的。如果它这样做了,滚动视图将停止调用 `itemBuilder`,即使它尚未达到 `itemCount`。
/// 通过返回 `null`,除非用户已经到达了 [ScrollView] 的末尾,否则 [ScrollPosition.maxScrollExtent] 将不准确。
/// 这也可能导致用户滚动时 [Scrollbar] 的增长。
///
/// 对于更准确的 [ScrollMetrics],请考虑指定 `itemCount`。
/// {@endtemplate}
///
/// `itemBuilder` 应始终在被调用时创建组件实例。
/// 避免使用返回先前构造的组件的构建器;如果列表视图的子组件是提前创建的,或者在创建 [ListView] 本身时一次性创建的,
/// 使用 [ListView] 构造函数会更高效。然而,更高效的方式是使用此构造函数的 `itemBuilder` 回调按需创建实例。
///
/// {@macro flutter.widgets.PageView.findChildIndexCallback}
///
/// `addAutomaticKeepAlives` 参数对应于 [SliverChildBuilderDelegate.addAutomaticKeepAlives] 属性。
/// `addRepaintBoundaries` 参数对应于 [SliverChildBuilderDelegate.addRepaintBoundaries] 属性。
/// `addSemanticIndexes` 参数对应于 [SliverChildBuilderDelegate.addSemanticIndexes] 属性。这些参数都不能为 null。
ListView.builder({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
this.itemExtent,
this.prototypeItem,
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,
}) : assert(itemCount == null || itemCount >= 0),
assert(semanticChildCount == null || semanticChildCount <= itemCount!),
assert(
itemExtent == null || prototypeItem == null,
'You can only pass itemExtent or prototypeItem, not both.',
),
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
findChildIndexCallback: findChildIndexCallback,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
semanticChildCount: semanticChildCount ?? itemCount,
);
/// 创建一个固定长度的可滚动线性数组,列表的"项"由列表项的"分隔符"分隔。
///
/// 对于具有大量项和分隔符子组件的列表视图,此构造函数是合适的,因为构建器只会为实际可见的子组件调用。
///
/// `itemBuilder` 回调将被调用大于等于零且小于 `itemCount` 的索引。
///
/// 分隔符只出现在列表项之间:分隔符 0 出现在项 0 之后,最后一个分隔符出现在最后一项之前。
///
/// `separatorBuilder` 回调将被调用大于等于零且小于 `itemCount - 1` 的索引。
///
/// `itemBuilder` 和 `separatorBuilder` 回调应始终在被调用时创建组件实例。
/// 避免使用返回先前构造的组件的构建器;如果列表视图的子组件是提前创建的,或者在创建 [ListView] 本身时一次性创建的,
/// 使用 [ListView] 构造函数会更高效。
///
/// {@macro flutter.widgets.ListView.builder.itemBuilder}
///
/// {@macro flutter.widgets.PageView.findChildIndexCallback}
///
/// {@tool snippet}
///
/// 这个示例展示了如何创建一个 [ListView],其 [ListTile] 列表项由 [Divider] 分隔。
///
/// ```dart
/// ListView.separated(
/// itemCount: 25,
/// separatorBuilder: (BuildContext context, int index) => const Divider(),
/// itemBuilder: (BuildContext context, int index) {
/// return ListTile(
/// title: Text('item $index'),
/// );
/// },
/// )
/// ```
/// {@end-tool}
///
/// `addAutomaticKeepAlives` 参数对应于 [SliverChildBuilderDelegate.addAutomaticKeepAlives] 属性。
/// `addRepaintBoundaries` 参数对应于 [SliverChildBuilderDelegate.addRepaintBoundaries] 属性。
/// `addSemanticIndexes` 参数对应于 [SliverChildBuilderDelegate.addSemanticIndexes] 属性。这些参数都不能为 null。
ListView.separated({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
required NullableIndexedWidgetBuilder itemBuilder,
ChildIndexGetter? findChildIndexCallback,
required IndexedWidgetBuilder separatorBuilder,
required int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
super.cacheExtent,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
}) : assert(itemCount >= 0),
itemExtent = null,
prototypeItem = null,
childrenDelegate = SliverChildBuilderDelegate(
(BuildContext context, int index) {
final int itemIndex = index ~/ 2;
if (index.isEven) {
return itemBuilder(context, itemIndex);
}
return separatorBuilder(context, itemIndex);
},
findChildIndexCallback: findChildIndexCallback,
childCount: _computeActualChildCount(itemCount),
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
semanticIndexCallback: (Widget widget, int index) {
return index.isEven ? index ~/ 2 : null;
},
),
super(
semanticChildCount: itemCount,
);
/// 使用自定义子模型创建一个可滚动的线性组件数组。
///
/// 例如,自定义子模型可以控制用于估计实际不可见的子组件大小的算法。
///
/// {@tool snippet}
///
/// 这个 [ListView] 使用自定义的 [SliverChildBuilderDelegate] 来支持子组件的重新排序。
///
/// ```dart
/// class MyListView extends StatefulWidget {
/// const MyListView({super.key});
///
/// @override
/// State<MyListView> createState() => _MyListViewState();
/// }
///
/// class _MyListViewState extends State<MyListView> {
/// List<String> items = <String>['1', '2', '3', '4', '5'];
///
/// void _reverse() {
/// setState(() {
/// items = items.reversed.toList();
/// });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: SafeArea(
/// child: ListView.custom(
/// childrenDelegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return KeepAlive(
/// data: items[index],
/// key: ValueKey<String>(items[index]),
/// );
/// },
/// childCount: items.length,
/// findChildIndexCallback: (Key key) {
/// final ValueKey<String> valueKey = key as ValueKey<String>;
/// final String data = valueKey.value;
/// return items.indexOf(data);
/// }
/// ),
/// ),
/// ),
/// bottomNavigationBar: BottomAppBar(
/// child: Row(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// TextButton(
/// onPressed: () => _reverse(),
/// child: const Text('Reverse items'),
/// ),
/// ],
/// ),
/// ),
/// );
/// }
/// }
///
/// class KeepAlive extends StatefulWidget {
/// const KeepAlive({
/// required Key key,
/// required this.data,
/// }) : super(key: key);
///
/// final String data;
///
/// @override
/// State<KeepAlive> createState() => _KeepAliveState();
/// }
///
/// class _KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin{
/// @override
/// bool get wantKeepAlive => true;
///
/// @override
/// Widget build(BuildContext context) {
/// super.build(context);
/// return Text(widget.data);
/// }
/// }
/// ```
/// {@end-tool}
const ListView.custom({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
this.itemExtent,
this.prototypeItem,
required this.childrenDelegate,
super.cacheExtent,
super.semanticChildCount,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
}) : assert(
itemExtent == null || prototypeItem == null,
// 你只能传入 itemExtent 或 prototypeItem,不能两者都传
'You can only pass itemExtent or prototypeItem, not both',
);
/// {@template flutter.widgets.list_view.itemExtent}
/// 如果非空,则强制子组件在滚动方向上具有给定的范围。
///
/// 指定 [itemExtent] 比让子组件确定自己的范围更高效,因为滚动机制可以利用对子组件范围的预知来节省工作,
/// 例如当滚动位置发生剧变时。
///
/// 另请参阅:
///
/// * [SliverFixedExtentList],当提供此属性时内部使用的 sliver。它约束其盒子子组件在主轴上具有特定的给定范围。
/// * [prototypeItem] 属性,它允许强制子组件的范围与给定的组件相同。
/// {@endtemplate}
final double? itemExtent;
/// {@template flutter.widgets.list_view.prototypeItem}
/// 如果非空,则强制子组件在滚动方向上具有与给定组件相同的范围。
///
/// 指定 [prototypeItem] 比让子组件确定自己的范围更高效,因为滚动机制可以利用对子组件范围的预知来节省工作,例如当滚动位置发生剧变时。
///
/// 另请参阅:
///
/// * [SliverPrototypeExtentList],当提供此属性时内部使用的 sliver。它约束其盒子子组件在主轴上具有与原型项相同的范围。
/// * [itemExtent] 属性,它允许强制子组件的范围为给定的值
/// {@endtemplate}
final Widget? prototypeItem;
/// 为 [ListView] 提供子组件的委托。
///
/// [ListView.custom] 构造函数让你可以明确地指定此委托。[ListView] 和 [ListView.builder] 构造函数创建一个 [childrenDelegate],
/// 分别包装给定的 [List] 和 [IndexedWidgetBuilder]。
final SliverChildDelegate childrenDelegate;
@override
Widget buildChildLayout(BuildContext context) {
if (itemExtent != null) {
return SliverFixedExtentList(
delegate: childrenDelegate,
itemExtent: itemExtent!,
);
} else if (prototypeItem != null) {
return SliverPrototypeExtentList(
delegate: childrenDelegate,
prototypeItem: prototypeItem!,
);
}
return SliverList(delegate: childrenDelegate);
}
buildChildLayout
方法是 ListView 类中的一个重要方法,它负责构建 ListView 的子布局。
这个方法接收一个 BuildContext 对象作为参数,然后根据 ListView 的属性来决定使用哪种类型的滑动列表。
- 如果
itemExtent
属性不为null
,则使用 SliverFixedExtentList。SliverFixedExtentList 是一种所有子项都有固定长度的滑动列表。itemExtent
属性表示每个子项的长度。 - 如果
itemExtent
为null
,但prototypeItem
不为null
,则使用 SliverPrototypeExtentList。SliverPrototypeExtentList 是一种所有子项都根据原型项prototypeItem
来决定长度的滑动列表。 - 如果
itemExtent
和prototypeItem
都为null
,则使用 SliverList。SliverList 是一种子项长度可以不同的滑动列表。
debugFillProperties
方法用于在调试时提供有关 ListView 的信息。这个方法会将 itemExtent
属性添加到 DiagnosticPropertiesBuilder 对象中。
_computeActualChildCount
是一个静态辅助方法,用于计算 ListView.separated 构造函数的实际子项数。这个方法接收一个 itemCount
参数,然后返回 itemCount * 2 - 1
和 0
中的较大值。这是因为在 ListView.separated 中,每两个子项之间都有一个 分隔器,所以实际的子项数是 itemCount
的两倍减一。
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
}
// 来计算separated构造函数的实际 child 数的帮助方法。
static int _computeActualChildCount(int itemCount) {
return math.max(0, itemCount * 2 - 1);
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)