类型

RenderBox

数据量固定的布局模型,类似column``row等组件

Sliver

数量不是固定的,需要加载列表来进行显示

Sliver组成部分

  • Scrollable :用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport 。
  • Viewport:显示的视窗,即列表的可视区域;
  • Sliver:视窗里显示的元素。

    布局过程

    可滚动视图归类 - 图1
  1. Scrollable 监听到用户滑动行为后,根据最新的滑动偏移构建 Viewport 。
  2. Viewport 将当前视口信息和配置信息通过 SliverConstraints 传递给 Sliver。
  3. Sliver 中对子组件(RenderBox)按需进行构建和布局,然后确认自身的位置、绘制等信息。

    Scrollable

    1. const Scrollable({
    2. Key? key,
    3. this.axisDirection = AxisDirection.down,
    4. this.controller,
    5. this.physics,
    6. required this.viewportBuilder,
    7. this.incrementCalculator
    8. this.excludeFromSemantics = false,
    9. this.semanticChildCount,
    10. this.dragStartBehavior = DragStartBehavior.start,
    11. this.restorationId,
    12. this.scrollBehavior,
    13. }) : assert(axisDirection != null),
    14. assert(dragStartBehavior != null),
    15. assert(viewportBuilder != null),
    16. assert(excludeFromSemantics != null),
    17. assert(semanticChildCount == null || semanticChildCount >= 0),
    18. super (key: key);
  • physics:此属性接受一个ScrollPhysics类型的对象。

ClampingScrollPhysics:列表滑动到边界时将不能继续滑动,Android效果 。
BouncingScrollPhysics:iOS 下弹性效果。

  • viewportBuilder:构建 Viewport 的回调。

    Viewport

    1. Viewport({
    2. Key? key,
    3. this.axisDirection = AxisDirection.down,
    4. this.crossAxisDirection,
    5. this.anchor = 0.0,
    6. required ViewportOffset offset, // 用户的滚动偏移
    7. // 类型为Key,表示从什么地方开始绘制,默认是第一个元素
    8. this.center,
    9. this.cacheExtent, // 预渲染区域
    10. //该参数用于配合解释cacheExtent的含义,也可以为主轴长度的乘数
    11. this.cacheExtentStyle = CacheExtentStyle.pixel,
    12. this.clipBehavior = Clip.hardEdge,
    13. List<Widget> slivers = const <Widget>[], // 需要显示的 Sliver 列表
    14. })

    Sliver

    Sliver 主要作用是对子组件进行构建和布局,比如 ListViewSliver 需要实现子组件(列表项)按需加载功能,只有当列表项进入预渲染区域时才会去对它进行构建和布局、渲染。

    常用可滑动组件

    SingleChildScrollView

    1. const SingleChildScrollView({
    2. Key? key,
    3. this.scrollDirection = Axis.vertical,
    4. this.reverse = false,
    5. this.padding,
    6. bool? primary,
    7. this.physics,
    8. this.controller,
    9. this.child,
    10. this.dragStartBehavior = DragStartBehavior.start,
    11. this.clipBehavior = Clip.hardEdge,
    12. this.restorationId,
    13. this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    14. }) : assert(scrollDirection != null),
    15. assert(dragStartBehavior != null),
    16. assert(clipBehavior != null),
    17. assert(!(controller != null && primary == true),
    18. ),
    19. primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
    20. super(key: key);

    用途:竖向布局的页面,例如一个页面可能在有些手机尺寸下不能放下或者是偏手机底部有输入框的页面。
    需要注意的是,通常**SingleChildScrollView**只应在期望的内容不会超过屏幕太多时使用,这是因为SingleChildScrollView不支持基于 Sliver 的延迟加载模型。

    ListView

    1. ListView({
    2. ...
    3. //可滚动widget公共参数
    4. Axis scrollDirection = Axis.vertical,
    5. bool reverse = false,
    6. ScrollController? controller,
    7. bool? primary,
    8. ScrollPhysics? physics,
    9. EdgeInsetsGeometry? padding,
    10. //ListView各个构造函数的共同参数
    11. double? itemExtent,
    12. Widget? prototypeItem,
    13. bool shrinkWrap = false,
    14. bool addAutomaticKeepAlives = true,
    15. bool addRepaintBoundaries = true,
    16. double? cacheExtent, // 预渲染区域长度
    17. //子widget列表
    18. List<Widget> children = const <Widget>[],
    19. })

    在具体的项目中应用

    1. ListView.builder(
    2. itemExtent: 49,
    3. padding: EdgeInsets.only(bottom: 80),
    4. itemCount: vm.buildingList.length,
    5. itemBuilder: (context, index) {
    6. return GestureDetector(
    7. onTap: () {},
    8. child: Container(),
    9. );
    10. },
    11. )

    itemExtent指的是ListView在其主轴方向上的长度。
    prototypeItem指的是列表子项长度相同但是不确定,这时候可以指定一个widget,但是和上面的itemExtent相斥。
    children参数,接收一个widget的数组,当然这个时候就可以把它当作SingleChildScrollView来使用了。这个就类似ios中的UITableView的作用了,更多负责视图的竖直列表。

    GridView

    组件是构建一个二维网格列表,类似于iOS中的UICollectionView

    1. GridView({
    2. Key? key,
    3. Axis scrollDirection = Axis.vertical,
    4. bool reverse = false,
    5. ScrollController? controller,
    6. bool? primary,
    7. ScrollPhysics? physics,
    8. bool shrinkWrap = false,
    9. EdgeInsetsGeometry? padding,
    10. required this.gridDelegate,
    11. bool addAutomaticKeepAlives = true,
    12. bool addRepaintBoundaries = true,
    13. double? cacheExtent,
    14. List<Widget> children = const <Widget>[],
    15. ...
    16. })

    SliverGridDelegate可以理解成组件的排列协议,主要是约束该组件的排列形式。

    1. SliverGridDelegateWithFixedCrossAxisCount({
    2. @required double crossAxisCount,
    3. double mainAxisSpacing = 0.0,
    4. double crossAxisSpacing = 0.0,
    5. double childAspectRatio = 1.0,
    6. })
  • crossAxisCount:横轴子元素的数量。

  • mainAxisSpacing:主轴方向的间距。
  • crossAxisSpacing:横轴方向子元素的间距。
  • childAspectRatio:子元素在横轴长度和主轴长度的比例。

当然当间距之间的数值都为0时,就可以用这个写法。

  1. GridView.count(
  2. crossAxisCount: 3,
  3. childAspectRatio: 1.0,
  4. children: <Widget>[],
  5. );

用法:

  1. GridView.builder(
  2. ...
  3. required SliverGridDelegate gridDelegate,
  4. required IndexedWidgetBuilder itemBuilder,
  5. )

一个负责GridView的样式,一个负责子Widget的布局样式。

PageView

  1. PageView({
  2. Key? key,
  3. this.scrollDirection = Axis.horizontal, // 滑动方向
  4. this.reverse = false,
  5. PageController? controller,
  6. this.physics,
  7. List<Widget> children = const <Widget>[],
  8. this.onPageChanged,
  9. //每次滑动是否强制切换整个页面,如果为false,则会根据实际的滑动距离显示页面
  10. this.pageSnapping = true,
  11. this.allowImplicitScrolling = false,
  12. this.padEnds = true,
  13. })

使用场景,无定时器的swiper页面,可以滑动切换的页面。
allowImplicitScrolling表示可以缓存两页数据,前后两页。

TabBarView

这个组件是对PageView的封装。

  1. TabBarView({
  2. Key? key,
  3. required this.children, // tab 页
  4. this.controller, // TabController
  5. this.physics,
  6. this.dragStartBehavior = DragStartBehavior.start,
  7. })

一般和TabBar一起使用,例如:

  1. return Material(
  2. child: Container(
  3. child: Column(
  4. mainAxisSize: MainAxisSize.max,
  5. children: <Widget>[
  6. Container(
  7. alignment: Alignment.center,
  8. width: MediaQuery.of(context).size.width,
  9. color: Colors.white,
  10. child: TabBar(
  11. indicatorColor: AppColors.primaryColor,
  12. indicatorSize: TabBarIndicatorSize.label,
  13. labelColor: AppColors.primaryColor,
  14. labelStyle: TextStyle(
  15. fontSize: 13,
  16. fontWeight: FontWeight.w800,
  17. ),
  18. unselectedLabelColor: AppColors.textLevel3,
  19. controller: _tabController,
  20. tabs: widget.vm.tabs
  21. .map((e) => Tab(
  22. text: e,
  23. ))
  24. .toList(),
  25. ),
  26. ),
  27. Expanded(
  28. child: TabBarView(
  29. controller: _tabController,
  30. children: [
  31. ],
  32. ),
  33. ),
  34. ],
  35. ),
  36. ),
  37. );

TabBar负责头部的文本绘制,包括底部的横线;TabController主要起到TabBarTabBarView中内容的桥梁;TabBarView则是单个页面内容。

CustomScrollView

使用场景:一个页面中包含多个可滑动的组件,例如ListView``GridView等。
可滚动视图归类 - 图2
column包裹的控件,会发现只会在各自的区域内滚动,而CustomScrollView 组件来帮助我们创建一个公共的 Scrollable 和 Viewport ,然后它的 slivers 参数接受一个 Sliver 数组,这样我们就可以使用CustomScrollView 方面的实现我们期望的功能了。这种控件是针对可滑动区域是整个页面的,例如一些首页的长视图页面。

  1. var listView = SliverFixedExtentList(
  2. itemExtent: 56,
  3. delegate: SliverChildBuilderDelegate(
  4. (_, index) => ListTile(title: Text('$index')),
  5. childCount: 10,
  6. ),
  7. );
  8. // 使用
  9. return CustomScrollView(
  10. slivers: [
  11. listView,
  12. listView,
  13. ],
  14. );
Sliver名称 功能 对应的可滚动组件
SliverList 列表 ListView
SliverFixedExtentList 高度固定的列表 ListView,指定itemExtent时
SliverAnimatedList 添加/删除列表项可以执行动画 AnimatedList
SliverGrid 网格 GridView
SliverPrototypeExtentList 根据原型生成高度固定的列表 ListView,指定prototypeItem 时
SliverFillViewport 包含多个子组件,每个都可以填满屏幕 PageView

除了和列表对应的 Sliver 之外还有一些用于对 Sliver 进行布局、装饰的组件,它们的子组件必须是 Sliver,我们列举几个常用的:

Sliver名称 对应 RenderBox
SliverPadding Padding
SliverVisibility、SliverOpacity Visibility、Opacity
SliverFadeTransition FadeTransition
SliverLayoutBuilder LayoutBuilder

还有一些其它常用的 Sliver:

Sliver名称 说明
SliverAppBar 对应 AppBar,主要是为了在 CustomScrollView 中使用。
SliverToBoxAdapter 一个适配器,可以将 RenderBox 适配为 Sliver,后面介绍。
SliverPersistentHeader 滑动到顶部时可以固定住。

这个作用更类似于iOS中的UIScorllerView