• Flutter笔记:聊一聊Flutter中委托的设计方法


    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 默认构造函数的实现

    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,
           );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    GridView 默认构造函数的结构可以看出,它本质上不过是创建了一个 SliverChildListDelegate 对象并赋值给 childrenDelegate

    SliverChildListDelegateSliverChildDelegate 的一个实现,它 使用一个固定的子组件列表来生成网格的子组件。这里,children 参数就是这个列表。另外,addAutomaticKeepAlivesaddRepaintBoundariesaddSemanticIndexes 参数用于控制子组件的生命周期、是否添加重绘边界和语义索引。

    另外一方面,该构造函数种调用了其父类(BoxScrollView)的构造函数。semanticChildCount 参数用于语义分析,它表示 GridView 中的子组件数量。如果 semanticChildCountnull,则使用 children.length 作为默认值。

    1.2 GridView.builder构造函数

    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,
           );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    从代码可以看到,这个构造函数接收多个参数,其中最重要的两个参数是 gridDelegateitemBuilder

    • gridDelegate 是一个 SliverGridDelegate 对象,它决定了网格的布局。这是一个必需的参数。
    • itemBuilder 是一个函数,它接收一个 BuildContext 和一个索引,然后返回一个 Widget。这个函数只会被调用大于等于零且小于 itemCount 的索引。这是一个必需的参数。

    GridView.builder 构造函数的工作原理是,当需要渲染一个子部件时,它会调用 itemBuilder 函数,传入当前的 BuildContext 和子部件的索引,然后将返回的 组件 添加到网格中。这样,只有当子部件实际需要显示时,才会调用 itemBuilder 函数创建子部件。

    此外,GridView.builder 还接收一些其他参数,如 itemCountaddAutomaticKeepAlivesaddRepaintBoundariesaddSemanticIndexes,这些参数用于控制 GridView 的行为。

    最后,GridView.builder 通过 SliverChildBuilderDelegate 创建了一个 childrenDelegate,然后传递给 GridView 的父类构造函数。这个 childrenDelegate 决定了如何为 GridView 创建子部件。

    1.3 GridView.custom构造函数

      /// 使用自定义 [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,
      });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    GridView.custom 构造函数用于创建一个可滚动的二维部件数组,它允许你完全自定义 **SliverGridDelegate **和 SliverChildDelegate

    • SliverGridDelegate 决定了网格的布局,例如每行的列数、每个子部件的尺寸等。
    • SliverChildDelegate 决定了如何生成网格的子部件。你可以使用 SliverChildBuilderDelegate 来按需生成子部件,或者使用 SliverChildListDelegate 来生成一个固定列表的子部件。

    GridView.custom 构造函数接收多个参数,其中最重要的两个参数是 gridDelegate 和 childrenDelegate,这两个参数都是必需的。

    • gridDelegate 是一个 SliverGridDelegate 对象,它决定了网格的布局。
    • childrenDelegate 是一个 SliverChildDelegate 对象,它决定了如何为 GridView 创建子部件。

    GridView.custom 会根据 gridDelegate 的设置来布局网格,然后调用 childrenDelegate 来生成子部件。这样,你可以完全自定义 GridView 的布局和子部件的生成方式。

    在这个构造函数的实现种:

    • gridDelegate 实现网格的布局工作:gridDelegateSliverGridDelegate 类型的对象,它是一个委托,负责定义网格的布局。具体来说,它决定了网格中每行的列数,以及每个格子的大小。当 GridView 需要布局其子部件时,它会调用 gridDelegate 的方法来获取布局信息。所以,你可以说 gridDelegate 委托了网格的布局工作。

    • childrenDelegate 实现子部件的创建工作:childrenDelegateSliverChildDelegate 类型的对象,它是一个委托,负责创建网格的子部件。具体来说,当 GridView 需要渲染一个新的子部件时,它会调用 childrenDelegate 的方法来创建这个子部件。

    1.4 GridView.count 构造函数

    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,
           );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    从代码可以看出,GridView.count 构造函数会根据 crossAxisCountmainAxisSpacingcrossAxisSpacingchildAspectRatio 的值来布局网格,然后根据 children 列表来创建子部件。这使得你可以轻松地创建一个具有固定列数的网格视图。

    GridView.count 构造函数中,gridDelegate 被设置为 SliverGridDelegateWithFixedCrossAxisCount 对象。这个对象会根据 crossAxisCountmainAxisSpacingcrossAxisSpacingchildAspectRatio 的值来布局网格。

    childrenDelegate 被设置为 SliverChildListDelegate 对象,它会根据传入的 children 列表来创建子部件。addAutomaticKeepAlivesaddRepaintBoundariesaddSemanticIndexes 参数会传递给 SliverChildListDelegate,用于控制子部件的生命周期、是否添加重绘边界和语义索引。

    1.5 GridView.extent 构造函数

    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,
           );
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    GridView.extent 构造函数会根据 maxCrossAxisExtentmainAxisSpacingcrossAxisSpacingchildAspectRatio 的值来布局网格,然后根据 children 列表来创建子部件。这使得你可以轻松地创建一个具有固定最大宽度的网格视图。

    GridView.extent 构造函数中,gridDelegate 被设置为 SliverGridDelegateWithMaxCrossAxisExtent 对象。这个对象会根据 maxCrossAxisExtentmainAxisSpacingcrossAxisSpacingchildAspectRatio 的值来布局网格。

    childrenDelegate 被设置为 SliverChildListDelegate 对象,它会根据传入的 children 列表来创建子部件。addAutomaticKeepAlivesaddRepaintBoundariesaddSemanticIndexes 参数会传递给 SliverChildListDelegate,用于控制子部件的生命周期、是否添加重绘边界和语义索引。

    2. 什么是委托

    2.1 Flutter中委托类的机制

    Flutter中,“Delegate” 结尾的类通常表示这些类的主要目的是"委托"(Delegate)某些工作或责任给其他类或者提供了一种扩展或自定义的机制。 —— 这个命名约定的目的是提醒开发者这些类通常用于 委托某种功能提供可插拔性

    具体来说,以下是一些常见情况,“Delegate” 结尾的类可能委托了什么:

    1. 业务逻辑委托:某些 “Delegate” 类可能用于将特定的业务逻辑委托给其他类。例如,一个 ListView.builder 中的 itemBuilder 参数允许你委托生成列表项的逻辑给开发者自定义的函数。

    2. 路由过渡委托:一些类用于定义页面路由之间的过渡效果,比如 PageRouteBuilder,允许开发者委托定义过渡效果的工作。

    3. 渲染和布局委托:在渲染和布局阶段,一些 “Delegate” 类允许你自定义渲染对象的外观和布局,除了这里介绍的 GridView使用两类 Delegate 分别实现提供子组件和子组件布局外——再比如 SliverPersistentHeaderDelegate 用于定义 SliverAppBar 的外观和行为。

    4. 插件或扩展机制:一些 “Delegate” 类可能用于实现插件或扩展的接口,这允许开发者自定义、扩展或替换某些功能的实现。

    5. 策略模式的委托:某些 “Delegate” 类实际上是策略模式的实现,允许你根据需要在不同的算法或行为之间切换,而不需要修改客户端代码。

    Delegate” 结尾的类通常表示它们起到了一种"桥梁"或"中介"的作用,将某些具体的实现委托给其他类,以实现灵活性、可定制性和可扩展性。这种命名约定有助于在代码中识别和理解这些类的用途和作用。

    2.1 设计原则与模式

    Flutter中以 “Delegate” 结尾的这些类通常,以分离不同部分之间的关注点并提供更好的可维护性和可扩展性。这些 “Delegate” 类通常遵循某些规则和设计原则,如以下所示:

    1. 分离关注点:“Delegate” 类有助于将代码拆分为不同的部分,每个部分关注特定的责任。这有助于减少代码的复杂性,提高可读性和可维护性。

    2. 单一责任原则(SRP):“Delegate” 类通常遵循 SRP,每个类关注一个单一的责任。这使得代码更容易测试、理解和维护。

    3. 策略模式:在许多情况下, “Delegate” 类实现策略模式,允许您根据需要在不同的算法或行为之间切换。这种设计模式有助于动态地选择不同的实现,而不需要修改客户端代码。

    4. 插件机制:许多 Delegate 类用于实现插件或扩展的接口,以便轻松添加和扩展功能。这样,开发人员可以创建自定义实现以满足其需求,而不必修改核心代码。

    这些 Delegate 类的共同特点是它们提供了灵活性、可扩展性和可定制性,使得 Flutter· 应用程序可以更容易地满足不同的需求。通过使用这些类,开发人员可以更轻松地实现一些通用的设计原则,例如单一责任原则和策略模式,以改进应用程序的结构和性能。

    3. 再看 GridView

    3.1 回顾

    本文 第1小节 介绍了 GridView 的用法。 GridView 有多种构造函数,其中一些构造函数使用了委托(Delegate)的概念,例如 GridView.builderGridView.custom,现在以这两个构造函数简单回顾一下。


    1. GridView.builder:这个构造函数允许你动态地创建网格项目。它需要一个 GridDelegate 和一个 IndexedWidgetBuilderGridDelegate 用于控制网格的布局,IndexedWidgetBuilder 用于生成网格项目。其中:
    • GridDelegate是一个抽象类,它有多个实现,例如:SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent。这些实现类允许你自定义网格的布局,例如设置交叉轴的项目数量或最大尺寸。

    • IndexedWidgetBuilder 是一个回调函数,它接收一个上下文和一个索引,返回一个新的组件。你可以在这个回调函数中根据索引生成不同的组件。


    1. GridView.custom:这个构造函数允许你使用自定义的 SliverChildDelegate 来生成网格项目。其中:
      • SliverChildDelegate 是一个抽象类,它有多个实现,例如SliverChildBuilderDelegateSliverChildListDelegate。这些实现类允许你自定义网格项目的生成方式,例如使用一个回调函数或一个固定的小组件列表。

    在这两个构造函数中,GridView 组件都使用了 Delegate 的类来提供灵活性和可扩展性。

    通过使用这些类,实现定义网格的布局和项目的生成方式,而不需要修改 GridView 组件的源代码。

    这应该算是一种的 策略模式 的应用,它 允许你在不同的策略之间动态地切换,以满足不同的需求

    进一步看 GridView 的代码,还需要在先介绍 GridViewgridDelegate 属性 和 childrenDelegate 属性以后,最后再看负责构建 GridView 的子布局的 buildChildLayout 方法。

    3.2 SliverGrid 组件的默认构造函数

    3.2.2 SliverChild 默认构造函数

    const SliverGrid({
      super.key,
      required super.delegate,
      required this.gridDelegate,
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • delegate:这是一个必需参数,类型为 SliverChildDelegate。它负责 提供和构建子组件
      而其中 SliverChildDelegate 包括:

      • SliverChildListDelegate(用于处理 固定数量 的子组件)
      • SliverChildBuilderDelegate(用于处理懒加载的子组件)。
    • gridDelegate:这也是一个必需参数,类型为 SliverGridDelegate。它负责控制 SliverGrid 的布局,例如每行的列数,子部件的长宽比,子部件之间的间距等。

      常见的 SliverGridDelegate 包括:

      • SliverGridDelegateWithFixedCrossAxisCount(固定列数)
      • SliverGridDelegateWithMaxCrossAxisExtent(最大交叉轴范围)。

    3.2.2 SliverChildDelegate

    SliverChildDelegate 是为 slivers 提供子组件的 抽象类

    许多 slivers 会懒加载它们的 box 子组件,以避免创建比视口可见的子组件更多。它们不是通过显式的 List 接收子组件,而是通过 SliverChildDelegate 接收子组件。

    【比较】
    SliverChildBuilderDelegate 是一个使用 构建器回调 来构造子组件的委托
    SliverChildListDelegate 是一个具有显式子列表的委托。

    子元素的生命周期阶段

    子元素的生命周期包括 创建(Creation)、销毁(Destruction) 和 销毁缓和(Destruction mitigation)三个阶段:

    (1)创建(Creation)阶段

    创建(Creation) 阶段,可见子组件的元素、状态和渲染对象将基于现有组件。
    SliverChildListDelegate 的情况 或 懒加载提供的组件(如 SliverChildBuilderDelegate 的情况)懒加载创建。

    (2)销毁阶段

    销毁阶段,当一个子组件滚动出视图时,关联的元素子树、状态和渲染对象被销毁。
    当它滚动回来时,一个新的子组件在 sliver 的同一位置将懒加载重新创建,同时新的元素、状态和渲染对象也会被创建。

    (3)销毁缓和(Destruction mitigation)阶段

    销毁缓和(Destruction mitigation) 阶段,为了在子元素滚动进出视图时 保留状态,有以下可能的选项:

    1. 将非琐碎的驱动 UI 状态的业务逻辑的所有权移出 sliver 子组件子树;
    2. KeepAlive 成为需要保留的 sliver 子组件子树的根组件;
    3. 使用 AutomaticKeepAlive 组件。

    如果在单个滚动视图中使用多个委托,每个委托的第一个子组件总是会被布局,即使它超出了当前可视区域。这是因为至少需要一个子组件来为整个滚动视图 estimateMaxScrollOffset,因为它使用当前构建的子组件来估计剩余子组件的范围。


    (1) SliverChildListDelegate extends SliverChildDelegate

    SliverChildListDelegate 是一个用于为 slivers 提供子组件的 委托Delegate,它使用一个明确的列表来生成子组件。这种方式很方便,但如果子组件的数量很大,或者子组件的创建成本很高,使用 SliverChildBuilderDelegate 或直接继承 SliverChildDelegate 来实现懒加载可能会更有效率。

    (2) SliverChildBuilderDelegate extends SliverChildDelegate

    SliverChildBuilderDelegate 是一个用于为 slivers 提供子组件的委托Delegate,它使用一个构建器回调来按需生成子组件。这种方式允许子组件进行懒加载,即只有当子组件真正需要显示时才会创建。这对于子组件数量大或者创建成本高的情况非常有用,因为它可以帮助我们避免不必要的资源浪费。此外,SliverChildBuilderDelegate 还提供了一些额外的控制,如是否为每个子组件添加自动保活(AutomaticKeepAlive)、重绘边界(RepaintBoundary)以及语义索引(SemanticIndex)。

    3.2.3 SliverGridDelegate

    (1) SliverGridDelegateWithFixedCrossAxisCount extends SliverGridDelegate

    SliverGridDelegateWithFixedCrossAxisCount 是一个用于为 slivers 提供网格布局的委托Delegate,它在交叉轴上具有固定数量的单元格。

    => 这意味着无论网格的总体大小如何,交叉轴上的单元格数量始终保持不变。这对于需要创建均匀分布的网格布局(例如图片网格)非常有用。此外,你还可以控制单元格之间的间距(mainAxisSpacingcrossAxisSpacing)、单元格的宽高比(childAspectRatio)以及网格的内边距(padding)。

    (2) SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate

    SliverGridDelegateWithMaxCrossAxisExtent 是一个用于为 slivers 提供网格布局的委托Delegate,它在交叉轴上的单元格具有最大扩展尺寸。

    => 这意味着单元格的尺寸会自动调整以适应网格的总体大小,但不会超过设定的最大尺寸。这对于需要创建响应式布局的网格非常有用,因为你可以根据屏幕的大小自动调整单元格的数量。此外,你还可以控制单元格之间的间距(mainAxisSpacingcrossAxisSpacing)、单元格的宽高比(childAspectRatio)以及网格的内边距(padding)。

    3.3 GridView 的 gridDelegate属性

    gridDelegate 是 GridView 类中的一个属性,它的类型是 SliverGridDelegate。这个属性是一个委托(delegate),它决定了 GridView 中子部件的布局。

    其源代码为:

      /// 一个委托,控制 [GridView] 中子部件的布局。
      ///
      /// [GridView],[GridView.builder] 和 [GridView.custom] 构造函数允许你明确指定这个委托。其他构造函数隐式创建一个 [gridDelegate]。
      final SliverGridDelegate gridDelegate;
    
    • 1
    • 2
    • 3
    • 4

    gridDelegate 属性的作用就是定义 GridView 中子部件的布局。这使得 GridView 可以灵活地适应各种需求,例如创建固定列数的网格,或者创建具有固定最大宽度的网格。

    SliverGridDelegate 是一个抽象类,它有两个常用的子类:SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent

    • SliverGridDelegateWithFixedCrossAxisCount 创建一个网格,其中交叉轴上有固定数量的格子。你可以指定交叉轴上的格子数量,以及格子之间的间距和宽高比。
    • SliverGridDelegateWithMaxCrossAxisExtent 创建一个网格,其中交叉轴上的每个格子都有最大的宽度。你可以指定每个格子的最大宽度,以及格子之间的间距和宽高比。

    GridViewGridView.builderGridView.custom 构造函数中,你可以明确指定 gridDelegate。在其他构造函数中,gridDelegate 会自动创建。

    3.4 GridView 的 childrenDelegate属性

      /// 一个委托,为 [GridView] 提供子部件。
      ///
      /// [GridView.custom] 构造函数允许你明确指定这个委托。其他构造函数创建一个包装给定子部件列表的 [childrenDelegate]。
      final SliverChildDelegate childrenDelegate;
    
    • 1
    • 2
    • 3
    • 4

    可以看到,childrenDelegate 属性类型为 SliverChildDelegate。这个属性是一个 委托(delegate),它决定了如何为 GridView 创建子部件。

    SliverChildDelegate 是一个抽象类,它有两个常用的子类:SliverChildListDelegateSliverChildBuilderDelegate。其中:

    • SliverChildListDelegate 接收一个固定长度的子部件列表,然后按照列表顺序创建子部件。
    • SliverChildBuilderDelegate 接收一个构建函数,然后按需创建子部件。这对于具有大量子部件的 GridView 非常有用,因为只有当子部件实际需要显示时,才会调用构建函数创建子部件。

    GridView.custom 构造函数中,你可以明确指定 childrenDelegate。在其他构造函数中,childrenDelegate 会自动创建,通常是包装给定的子部件列表。

    因此,childrenDelegate 属性的作用就是定义如何为 GridView 创建子部件。这使得 GridView 可以灵活地适应各种需求,例如创建固定数量的子部件,或者按需创建子部件。

    3.5 GridView 的 buildChildLayout 方法

    buildChildLayout 负责构建 GridView 的子布局。

    buildChildLayout 方法是 GridView 的核心,它负责构建 GridView 的子布局。这个方法的实现非常简单,只是创建了一个 SliverGrid 对象,并将 childrenDelegate 和 gridDelegate 传递给了它。它的代码如下:

      
      Widget buildChildLayout(BuildContext context) {
        return SliverGrid(
          delegate: childrenDelegate, // SliverChildDelegate 对象,它决定了如何创建和布局子项。
          gridDelegate: gridDelegate, // SliverGridDelegate 对象,它决定了网格的布局。
        );
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以看到,负责构建 GridView 的子布局的 实际上是通过 SliverGrid实现的,其中需要指定两个信息:

    1. 如何创建和布局子项:delegate参数;
      SliverGriddelegate 参数类型为 SliverChildDelegate,它负责提供和构建子部件。childrenDelegate 是传递给 delegate 的参数,它可能是 SliverChildListDelegate(用于处理固定数量的子部件)或 SliverChildBuilderDelegate(用于处理懒加载的子部件)。
    2. 网格的布局:gridDelegate参数。

    3.6 这就是Flutter设计中的 委托

    GridViewbuildChildLayout 方法中,我们可以看到委托(Delegate)的强大之处。

    GridView 本身并没有实现如何创建和布局子项,以及如何布局网格,而是将这些工作委托给了 childrenDelegate 和 gridDelegate。

    childrenDelegate 是一个 SliverChildDelegate 对象,它决定了如何创建和布局子项。这个委托可以是 SliverChildListDelegate(用于处理固定数量的子部件)或 SliverChildBuilderDelegate(用于处理懒加载的子部件)。通过这个委托,GridView 可以灵活地创建和布局子项,无论是固定数量的子部件,还是懒加载的子部件。

    gridDelegate 是一个 SliverGridDelegate 对象,它决定了网格的布局。这个委托可以是 SliverGridDelegateWithFixedCrossAxisCount(固定列数的网格)或 SliverGridDelegateWithMaxCrossAxisExtent(最大交叉轴范围的网格)。通过这个委托,GridView 可以灵活地布局网格,无论是固定列数的网格,还是最大交叉轴范围的网格。

    总的来说,通过委托(Delegate)的方式,GridView 将创建和布局子项,以及布局网格的工作交给了其他对象,从而实现了代码的解耦和复用。

    4. 小结与启示

    Flutter 中,委托(Delegate)类 可以认为是一种 设计模式,它允许一个类将某些任务或责任委托给另一个类。这种设计模式可以提高代码的 灵活性可复用性,因为它允许我们在不修改原有类的情况下,改变或扩展某些功能。

    GridView 的例子中,我们看到了两种类型的委托:SliverChildDelegateSliverGridDelegate。SliverChildDelegate 负责创建和布局子项,而 SliverGridDelegate 负责布局网格。通过这两个委托,GridView 可以灵活地创建和布局子项,以及布局网格,而无需自己实现这些功能。

    这种设计模式的优点是:

    • 分离关注点:委托允许我们将不同的功能分离到不同的类中,每个类只关注一个特定的任务或责任。这有助于减少代码的复杂性,提高可读性和可维护性。

    • 提高复用性:通过委托,我们可以在不同的上下文中复用同一个类。例如,我们可以在不同的 GridView 中复用同一个 SliverChildDelegate 或 SliverGridDelegate。

    • 提高灵活性:通过委托,我们可以在运行时改变某些功能的实现。例如,我们可以在运行时改变 GridView 的 gridDelegate,从而改变网格的布局。

    这种模式可以应用于以后的编程中。例如,假设我们正在设计一个自定义的列表组件 MyListView,我们可以设计两个委托:ItemBuilderItemLayoutDelegateItemBuilder 负责创建列表项,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);
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过这种方式,我们可以在不修改 CustomListView 的情况下,改变列表项的创建方式和布局方式,只需要提供不同的 ItemBuilder 和 ItemLayoutDelegate 即可。这就是委托(Delegate)设计模式的强大之处。

    这种委托(Delegate)编程模式在 Flutter 中得到了广泛的应用。通过理解和掌握这种设计模式,我们可以更好地理解和使用 Flutter,以及编写出更灵活、更可复用的代码。

    附录

    F.1 Flutter中的一些以Delegate结尾的类(表)

    以下是类名和它们的描述的表格形式:

    类名描述
    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委托,通常用于资源管理和隔离。

    F.2 SliverChildDelegate源码

    /// 为 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;
    
      
      String toString() {
        final List<String> description = <String>[];
        debugFillDescription(description);
        return '${describeIdentity(this)}(${description.join(", ")})';
      }
    
       /// 为 [toString] 使用,向给定的描述中添加额外的信息。
      
      
      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})');
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152

    F.3 SliverChildListDelegate源码

    /// 使用明确列表为 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 {
      ///   final List _children = [];
      ///
      ///   void someHandler() {
      ///     setState(() {
      ///       // 这里的 key 允许 Flutter 重用底层的渲染对象,即使子组件列表被重新创建。
      ///       _children.add(ChildWidget(key: UniqueKey()));
      ///     });
      ///   }
      ///
      ///   @override
      ///   Widget build(BuildContext context) {
      ///     // 由于 Widget 是不可变的,所以总是创建一个新的子组件列表。
      ///     return PageView(children: List.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;
      }
    
      
      int? findIndexByKey(Key key) {
        final Key childKey;
        if (key is _SaltedValueKey) {
          final _SaltedValueKey saltedValueKey = key;
          childKey = saltedValueKey.value;
        } else {
          childKey = key;
        }
        return _findChildIndex(childKey);
      }
    
      
      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);
      }
    
      
      int? get estimatedChildCount => children.length;
    
      
      bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
        return children != oldDelegate.children;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190

    F.4 SliverChildBuilderDelegate源码

    /// 使用构建器回调为 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: [
    ///     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: [
    ///     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;
    
      
      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);
      }
    
      
      ('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);
      }
    
      
      int? get estimatedChildCount => childCount;
    
      
      bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
  • 相关阅读:
    Java_代码块/单例设计模式(饿汉式 / 懒汉式)
    Java 与 Go:可变数组
    Python10-使用urllib模块处理URL
    详解 spring data jpa,全方位总结,干货分享
    Flink测试利器之DataGen初探
    Leetcode 1417. Reformat The String
    最小二乘估计心得
    利用宝塔实现百度自动推送
    Arcgis小技巧【14】——拓扑(Topology)的方方面面
    [附源码]计算机毕业设计网上电影购票系统Springboot程序
  • 原文地址:https://blog.csdn.net/qq_28550263/article/details/134056041