Flutter.源码分析ListViewflutter/packages/flutter/lib/src/widgets/scroll_view.dart/ListView
1. 类注释部分
/// 一个线性排列的可滚动组件列表。 /// /// {@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{ // ... }
2. 默认构造函数注释部分
/// 从显式的 [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, );
3. ListView.builder构造函数注释部分
/// 创建一个可滚动的线性组件数组,这些组件是按需创建的。 /// /// 对于具有大量(或无限)子组件的列表视图,此构造函数是合适的,因为构建器只会为实际可见的子组件调用。 /// /// 提供非空的 `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, );
4. ListView.separated构造函数注释部分
/// 创建一个固定长度的可滚动线性数组,列表的"项"由列表项的"分隔符"分隔。 /// /// 对于具有大量项和分隔符子组件的列表视图,此构造函数是合适的,因为构建器只会为实际可见的子组件调用。 /// /// `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, );
5. ListView.custom构造函数注释部分
/// 使用自定义子模型创建一个可滚动的线性组件数组。 /// /// 例如,自定义子模型可以控制用于估计实际不可见的子组件大小的算法。 /// /// {@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', );
6. itemExtent属性
/// {@template flutter.widgets.list_view.itemExtent} /// 如果非空,则强制子组件在滚动方向上具有给定的范围。 /// /// 指定 [itemExtent] 比让子组件确定自己的范围更高效,因为滚动机制可以利用对子组件范围的预知来节省工作, /// 例如当滚动位置发生剧变时。 /// /// 另请参阅: /// /// * [SliverFixedExtentList],当提供此属性时内部使用的 sliver。它约束其盒子子组件在主轴上具有特定的给定范围。 /// * [prototypeItem] 属性,它允许强制子组件的范围与给定的组件相同。 /// {@endtemplate} final double? itemExtent;
7. prototypeItem属性
/// {@template flutter.widgets.list_view.prototypeItem} /// 如果非空,则强制子组件在滚动方向上具有与给定组件相同的范围。 /// /// 指定 [prototypeItem] 比让子组件确定自己的范围更高效,因为滚动机制可以利用对子组件范围的预知来节省工作,例如当滚动位置发生剧变时。 /// /// 另请参阅: /// /// * [SliverPrototypeExtentList],当提供此属性时内部使用的 sliver。它约束其盒子子组件在主轴上具有与原型项相同的范围。 /// * [itemExtent] 属性,它允许强制子组件的范围为给定的值 /// {@endtemplate} final Widget? prototypeItem;
8. childrenDelegate属性
/// 为 [ListView] 提供子组件的委托。 /// /// [ListView.custom] 构造函数让你可以明确地指定此委托。[ListView] 和 [ListView.builder] 构造函数创建一个 [childrenDelegate], /// 分别包装给定的 [List] 和 [IndexedWidgetBuilder]。 final SliverChildDelegate childrenDelegate;
9. buildChildLayout
@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
的两倍减一。
10. 其它部分
@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); }