前面介绍的 Flex、Row、Column 都是不可滑动的,如果当子 Widget 的大小超过 Flex、Row 或 Column 之后,就会报 OverLoad,会在界面上看到黄黑色的条;还有一种情况就是需要在页面上显示很多的列表项。所以为了显示这些长度超过屏幕范围的 Widget 和列表项,Flutter 提供了多种可滚动的 Widget,这些可滚动 Widget 的使用范围都不同,虽然也有 ScrollView 和 ListView,但和 Android、iOS 里的用法都略有不同,下面会仔细介绍。

SingleChildScrollView

SingleChildScrollView 是只能包含一个子 Widget 的可滚动 Widget。如果有很多子 Widget,那么需要用 ListBody 或者 Column 或者其他 Widget 来嵌套这些子 Widget,然后在 SingleChildScrollView 里使用。

快速上手

SingleChildScrollView 可以让 Widget 具有滑动的功能,而且可以指定滚动的方向,其 child 参数就是你想要添加滚动功能的 Widget,例如:

  1. SingleChildScrollView(
  2. scrollDirection: Axis.horizontal,
  3. child: Row(
  4. children: <Widget>[Text('Hello Flutter ' * 100)],
  5. ),
  6. )

可以将前面子 Widget 超过父容器时出现 overflowed 错误的代码更改为:

  1. import 'package:flutter/material.dart';
  2. main() {
  3. runApp(new MyApp());
  4. }
  5. class MyApp extends StatelessWidget {
  6. @override
  7. Widget build(BuildContext context) {
  8. return new MaterialApp(
  9. title: 'Test',
  10. home: new Scaffold(
  11. appBar: new AppBar(title: new Text('Flutter 可滚动Widget --SingleChildScrollView')),
  12. body: SingleChildScrollView(
  13. scrollDirection: Axis.horizontal,
  14. child: Row(
  15. children: <Widget>[Text('Hello Flutter ' * 100)],
  16. ),
  17. )));
  18. }
  19. }

运行效果为:

Flutter 学习(二十六)可滚动 Widget - 图1

这里的文本内容没有了 overflowed 错误的黄黑色的条,而且可以在水平方向上滑动。

构造函数及参数说明

先看 SingleChildScrollView 的构造函数:

  1. class SingleChildScrollView extends StatelessWidget {
  2. const SingleChildScrollView({
  3. Key key,
  4. this.scrollDirection = Axis.vertical,
  5. this.reverse = false,
  6. this.padding,
  7. bool primary,
  8. this.physics,
  9. this.controller,
  10. this.child,
  11. this.dragStartBehavior = DragStartBehavior.down,
  12. })
  13. ...
  14. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
scrollDirection Axis 滑动的方向
默认为 Axis.vertical,垂直方向可滑动
可选
padding EdgeInsetsGeometry SingleChildScrollView 插入 子Widget 时的内边距 可选
reverse bool 控制 SingleChildScrollView 是从头开始滑,还是从尾开始滑,默认为 false,就是从头开始滑
例如当 scrollDirection 为 Axis.vertical,即垂直方向可滑动,那么 reverse 为 false,就是从头部滑到底部,当 reverse 为 true 时,就是从底部滑到头部
可选
primary bool 是否是与父级关联的主滚动视图
当为 true 时,即使 SingleChildScrollView 里没有足够的内容也能滑动,
当 scrollDirection 为 Axis.vertical,且 controller 为 null 时,默认为 true
可选
physics ScrollPhysics 设置 SingleChildScrollView 的滚动效果
如果想让 SingleChildScrollView 里没有足够的内容也能滑动,则设置为 AlwaysScrollableScrollPhysics()
如果想让 SingleChildScrollView 在没有足够的内容的时候不能滑动,则设置为 ScrollPhysics()
可选
controller ScrollController 可以控制 SingleChildScrollView 滚动的位置
当 primary 为 true 时,controller 必须为 null
ScrollController 提供以下的几个功能:
1.设置 SingleChildScrollView 滑动的初始位置
2.可以控制 SingleChildScrollView 是否存储和恢复滑动的位置
3.可以读取、设置当前滑动的位置
可选
children List SingleChildScrollView 的列表项 可选
dragStartBehavior DragStartBehavior 确定处理拖动开始行为的方式。
如果设置为[DragStartBehavior.start],则在检测到拖动手势时将开始滚动拖动行为
如果设置为[DragStartBehavior.down],它将在首次检测到向下事件时开始
可选

ListView

ListView 是可以线性排列子 Widget 的可滚动 Widget。ListView 可以和数据绑定用来实现瀑布流。如果有很多子 Widget,能使用 ListView 的就不要使用 SingleChildScrollView,因为 ListView 的性能比 SingleChildView 好。

ListView 的辅助 Widget

还有一些 Widget 可以辅助 ListView 的使用,例如:

  1. ListTile:一个固定高度的行,通常包含一些文本,以及一个行前或行尾图标。
  2. Stepper:一个 Material Design 步骤指示器,显示一系列步骤的过程
  3. Divider:一个逻辑 1 像素厚的水平分割线,两边都有填充

快速上手

有四种使用 ListView 的方法:

使用默认的构造函数,给 children 属性赋值

**
使用默认构造函数写 ListView,需要给 children 属性赋值,但只适用于那些只有少量子 Widget 的 ListView,ListView 创建的时候,其子 Widget 也会一起创建。

Demo 如下:

  1. import 'package:flutter/material.dart';
  2. main() => runApp(new ListViewDefaultWidget());
  3. class ListViewDefaultWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return new MaterialApp(
  7. title: 'Test',
  8. home: new Scaffold(
  9. appBar:
  10. new AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
  11. body: ListView(
  12. children: <Widget>[
  13. ListTile(title: Text('Title1')),
  14. ListTile(title: Text('Title2')),
  15. ListTile(title: Text('Title3')),
  16. ListTile(title: Text('Title4')),
  17. ListTile(title: Text('Title5')),
  18. ListTile(title: Text('Title6')),
  19. ListTile(title: Text('Title7')),
  20. ListTile(title: Text('Title8')),
  21. ListTile(title: Text('Title9')),
  22. ListTile(title: Text('Title10')),
  23. ListTile(title: Text('Title11')),
  24. ListTile(title: Text('Title12')),
  25. ListTile(title: Text('Title13')),
  26. ListTile(title: Text('Title14')),
  27. ListTile(title: Text('Title15')),
  28. ListTile(title: Text('Title16')),
  29. ListTile(title: Text('Title17')),
  30. ListTile(title: Text('Title18')),
  31. ListTile(title: Text('Title19')),
  32. ],
  33. )));
  34. }
  35. }

运行效果为:

Flutter 学习(二十六)可滚动 Widget - 图2

使用 ListView.builder,可用于和数据绑定实现大量或无限的列表

ListView.builder 可以用于构建大量或无限的列表,是因为 ListView.builder 只会构建那些实际可见的子 Widget。
ListView.builder 的定义为:

  1. ListView.builder({
  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. this.itemExtent,
  11. @required IndexedWidgetBuilder itemBuilder,
  12. int itemCount,
  13. bool addAutomaticKeepAlives = true,
  14. bool addRepaintBoundaries = true,
  15. bool addSemanticIndexes = true,
  16. double cacheExtent,
  17. int semanticChildCount,
  18. DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  19. })
  20. ...

大部分属性都和 ListView 的默认构造函数一样,除了这两个:

  • int itemCount
    代表子 Widget 的数量,虽然是可选的,但是还是建议赋值,可以让 ListView 预估最大滑动距离,从而提升性能。如果为 null,则子节点数由 [itemBuilder] 返回 null 的最小索引确定。
  • @required IndexedWidgetBuilder itemBuilder
    itemBuilder 用于创建实际可见的子 Widget,只有索引大于或等于零且小于 itemCount 才会调用 itemBuilder。

下面写一个数据和 ListView.builder 绑定使用的例子:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(ListViewBuilderWidget(
  3. items: List<String>.generate(10000, (i) => "Item $i"),
  4. ));
  5. class ListViewBuilderWidget extends StatelessWidget {
  6. final List<String> items;
  7. ListViewBuilderWidget({Key key, @required this.items}) : super(key: key);
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'Test',
  12. home: Scaffold(
  13. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
  14. body: ListView.builder(
  15. itemCount: items.length,
  16. itemBuilder: (context, index) {
  17. return ListTile(
  18. title: Text('${items[index]}'),
  19. );
  20. },
  21. ),
  22. ),
  23. );
  24. }
  25. }

运行后的效果为:
Flutter 学习(二十六)可滚动 Widget - 图3
要实现一个无限循环列表,只要不给 itemCount 赋值就行,如下:

  1. ListView.builder(
  2. padding: EdgeInsets.all(8.0),
  3. itemBuilder: (BuildContext context, int index) {
  4. return ListTile(title: Text('Title $index'),);
  5. },
  6. )

使用 ListView.separated,具有分割项的 ListView.builder

看 ListView.separated 的定义:

  1. ListView.separated({
  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 IndexedWidgetBuilder itemBuilder,
  11. @required IndexedWidgetBuilder separatorBuilder,
  12. @required int itemCount,
  13. bool addAutomaticKeepAlives = true,
  14. bool addRepaintBoundaries = true,
  15. bool addSemanticIndexes = true,
  16. double cacheExtent,
  17. })
  18. ...

相比 ListView.builder 多了一个 separatorBuilder,separatorBuilder 就是用于构建分割项的,而且 itemBuilder、separatorBuilder、itemCount 都是必选的。

使用的 demo 如下:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(ListViewSeparatedWidget(
  3. items: List<String>.generate(10000, (i) => "Item $i"),
  4. ));
  5. class ListViewSeparatedWidget extends StatelessWidget {
  6. final List<String> items;
  7. ListViewSeparatedWidget({Key key, @required this.items}) : super(key: key);
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'Test',
  12. home: Scaffold(
  13. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
  14. body: ListView.separated(
  15. itemCount: items.length,
  16. itemBuilder: (context, index) {
  17. return ListTile(
  18. title: Text('${items[index]}'),
  19. );
  20. },
  21. separatorBuilder: (context, index) {
  22. return Container(
  23. constraints: BoxConstraints.tightFor(height: 10),
  24. color: Colors.orange,
  25. );
  26. },
  27. ),
  28. ),
  29. );
  30. }
  31. }

运行效果为:
Flutter 学习(二十六)可滚动 Widget - 图4

使用 ListView.custom,需要使用 SliverChildDelegate

SliverChildDelegate 提供了定制子 Widget 的能力。

首先看 ListView.custom 的定义:

  1. const ListView.custom({
  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. this.itemExtent,
  11. @required this.childrenDelegate,
  12. double cacheExtent,
  13. int semanticChildCount,
  14. })

childrenDelegate 为必选参数,在看如何实现 SliverChildDelegate,发现 SliverChildDelegate 是一个抽象类,SliverChildDelegate 的 build 方法可以对单个子 Widget 进行自定义处理,而且 SliverChildDelegate 有个默认实现 SliverChildListDelegate,所以我们用 SliverChildListDelegate 来实现 ListView.custom,代码如下:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(ListViewCustomWidget(
  3. items: List<String>.generate(10000, (i) => "Item $i"),
  4. ));
  5. class ListViewCustomWidget extends StatelessWidget {
  6. final List<String> items;
  7. ListViewCustomWidget({Key key, @required this.items}) : super(key: key);
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'Test',
  12. home: Scaffold(
  13. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
  14. body: ListView.custom(
  15. childrenDelegate: SliverChildListDelegate(<Widget>[
  16. ListTile(title: Text('Title1')),
  17. ListTile(title: Text('Title2')),
  18. ListTile(title: Text('Title3')),
  19. ListTile(title: Text('Title4')),
  20. ListTile(title: Text('Title5')),
  21. ListTile(title: Text('Title6')),
  22. ListTile(title: Text('Title7')),
  23. ListTile(title: Text('Title8')),
  24. ListTile(title: Text('Title9')),
  25. ListTile(title: Text('Title10')),
  26. ListTile(title: Text('Title11')),
  27. ListTile(title: Text('Title12')),
  28. ListTile(title: Text('Title13')),
  29. ListTile(title: Text('Title14')),
  30. ListTile(title: Text('Title15')),
  31. ListTile(title: Text('Title16')),
  32. ListTile(title: Text('Title17')),
  33. ListTile(title: Text('Title18')),
  34. ListTile(title: Text('Title19')),
  35. ]),
  36. ),
  37. ),
  38. );
  39. }
  40. }

运行效果为:
Flutter 学习(二十六)可滚动 Widget - 图5

构造函数及参数使用

首先看 ListView 的构造函数:

  1. class ListView extends BoxScrollView {
  2. ListView({
  3. Key key,
  4. Axis scrollDirection = Axis.vertical,
  5. bool reverse = false,
  6. ScrollController controller,
  7. bool primary,
  8. ScrollPhysics physics,
  9. bool shrinkWrap = false,
  10. EdgeInsetsGeometry padding,
  11. this.itemExtent,
  12. bool addAutomaticKeepAlives = true,
  13. bool addRepaintBoundaries = true,
  14. bool addSemanticIndexes = true,
  15. double cacheExtent,
  16. List<Widget> children = const <Widget>[],
  17. int semanticChildCount,
  18. DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  19. })
  20. ...
  21. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
scrollDirection Axis 滑动的方向
默认为 Axis.vertical,垂直方向可滑动
可选
reverse bool 控制 ListView 里列表项的排列顺序,是按照插入顺序排,还是按照插入顺序相反的方向排序。
默认为 false,就是按照插入顺序排序,第一个插入的在头部
,当 reverse 为 true 时,第一个插入的会在底部
可选
controller ScrollController 可以控制 ListView 滚动的位置
ScrollController 提供以下的几个功能:
1.设置 ListView 滑动的初始位置
2.可以控制 ListView 是否存储和恢复滑动的位置
3.可以读取、设置当前滑动的位置
可以继承 ScrollController 实现自定义的功能
当 primary 为 true 时,controller 必须为 null
可选
primary bool 是否是与父级关联的主滚动视图
当为 true 时,即使 ListView 里没有足够的内容也能滑动
可选
physics ScrollPhysics 设置 ListView 的滚动效果
值必须为 ScrollPhysics 的子类,比如有如下的值:
AlwaysScrollableScrollPhysics():可以让 ListView 里没有足够的内容也能滑动
ScrollPhysics():ListView 在没有足够的内容的时候不能滑动
可选
shrinkWrap bool 是否根据列表项的总长度来设置 ListView的长度,默认值为 false。
当 shrinkWrap 为 false 时,ListView 会在滚动方向扩展到可占用的最大空间
当 shrinkWrap 为 true 时,ListView 在滚动方向占用的空间就是其列表项的总长度,但是这样会很耗性能,因为当其列表项发生变化时,ListView 的大小会重新计算
可选
padding EdgeInsetsGeometry ListView 的内边距 可选
itemExtent double itemExtent 指的是列表项的大小
如果滚动方向是垂直方向,则 itemExtent 代表的是子 Widget 的高度,
如果滚动方向为水平方向,则 itemExtent 代表的是子 Widget 的长度
如果 itemExtent 不为 null,则会强制所有子 Widget 在滑动方向的大小都为 itemExtent
指定 itemExtent 会比较高效,因为子 Widget 的高度就不需要在去计算,ListView 也可以提前知道列表的长度
可选
addAutomaticKeepAlives bool 是否用 AutomaticKeepAlive 来包列表项,默认为 true
在一个 lazy list 里,如果子 Widget 为了保证自己在滑出可视界面时不被回收,就需要把 addAutomaticKeepAlives 设为 true
当子 Widget 不需要让自己保持存活时,为了提升性能,请把 addAutomaticKeepAlives 设为 false
如果 子Widget 自己维护其 KeepAlive 状态,那么此参数必须置为false。
可选
addRepaintBoundaries bool 是否用 RepaintBoundary 来包列表项,默认为 true
当 addRepaintBoundaries 为 true 时,可以避免列表项重绘,提高性能
但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加 RepaintBoundary 反而会更高效。
可选
addSemanticIndexes bool 是否用 IndexedSemantics 来包列表项,默认为 true
使用 IndexedSemantics 是为了正确的用于辅助模式
可选
cacheExtent double ListView 可见部分的前面和后面的区域可以用来缓存列表项,
这部分区域的 item 即使不可见,也会加载出来,所以当滑动到这个区域的时候,缓存的区域就会变的可见,
cacheExtent 就表示缓存区域在可见部分的前面和后面有多少像素
可选
children List ListView 的列表项 可选
semanticChildCount int 提供语义信息的列表项的数量
默认为 ListView 的 item 的数量
可选
dragStartBehavior DragStartBehavior 确定处理拖动开始行为的方式。
如果设置为[DragStartBehavior.start],则在检测到拖动手势时将开始滚动拖动行为
如果设置为[DragStartBehavior.down],它将在首次检测到向下事件时开始
可选

CustomScrollView

CustomScrollView 是可以使用 slivers 来自定义滑动效果的可滚动 Widget。

slivers

slivers 指的是以 Sliver 开头的一系列 Widget,例如:SliverList、SliverGrid、SliverAppBar 等,Sliver 有“小片”的意思,在 Flutter 中,指的是具有特定滚动效果的可滚动块,它们只能用在 CustomScrollView 里,多个 Sliver 拼在 CustomScrollView 里来实现特定的效果。

CustomScrollView 也可以实现 ListView 的功能

CustomScrollView 使用 SliverList 可以实现和 ListView 一样的功能,所以:

  1. CustomScrollView 不像 SingleChildScrollView 一样只能包含一个子 Widget,
  2. CustomScrollView 可以实现比 ListView 更复杂的滑动效果,例如:吸顶,所以当 ListView 不能实现一些滑动效果时,就应该使用 CustomScrollView,但是如果没有特殊的效果,而是数据展示,就使用 ListView。

使用

CustomScrollView 里面可以添加多个 Widget,而且可以为 Widget 提供复杂的滑动效果,需要为其 slivers 参数赋值,而且 slivers 参数只能接受特定的 Widget,例如:

  1. CustomScrollView(
  2. slivers: <Widget>[
  3. const SliverAppBar(
  4. pinned: true,
  5. expandedHeight: 250.0,
  6. flexibleSpace: FlexibleSpaceBar(
  7. title: Text('Demo'),
  8. ),
  9. ),
  10. SliverGrid(
  11. gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
  12. maxCrossAxisExtent: 200.0,
  13. mainAxisSpacing: 10.0,
  14. crossAxisSpacing: 10.0,
  15. childAspectRatio: 4.0,
  16. ),
  17. delegate: SliverChildBuilderDelegate(
  18. (BuildContext context, int index) {
  19. return Container(
  20. alignment: Alignment.center,
  21. color: Colors.teal[100 * (index % 9)],
  22. child: Text('grid item $index'),
  23. );
  24. },
  25. childCount: 20,
  26. ),
  27. ),
  28. SliverFixedExtentList(
  29. itemExtent: 50.0,
  30. delegate: SliverChildBuilderDelegate(
  31. (BuildContext context, int index) {
  32. return Container(
  33. alignment: Alignment.center,
  34. color: Colors.lightBlue[100 * (index % 9)],
  35. child: Text('list item $index'),
  36. );
  37. },
  38. ),
  39. ),
  40. ],
  41. )

CustomScrollView 在一个页面使用的 Demo 为:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(CustomScrollViewWidget());
  3. class CustomScrollViewWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Test',
  8. home: Scaffold(
  9. appBar:
  10. AppBar(title: new Text('Flutter 可滚动Widget -- CustomScrollView')),
  11. body: CustomScrollView(
  12. slivers: <Widget>[
  13. const SliverAppBar(
  14. pinned: true,
  15. expandedHeight: 250.0,
  16. flexibleSpace: FlexibleSpaceBar(
  17. title: Text('Demo'),
  18. ),
  19. ),
  20. SliverGrid(
  21. gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
  22. maxCrossAxisExtent: 200.0,
  23. mainAxisSpacing: 10.0,
  24. crossAxisSpacing: 10.0,
  25. childAspectRatio: 4.0,
  26. ),
  27. delegate: SliverChildBuilderDelegate(
  28. (BuildContext context, int index) {
  29. return Container(
  30. alignment: Alignment.center,
  31. color: Colors.teal[100 * (index % 9)],
  32. child: Text('grid item $index'),
  33. );
  34. },
  35. childCount: 20,
  36. ),
  37. ),
  38. SliverFixedExtentList(
  39. itemExtent: 50.0,
  40. delegate: SliverChildBuilderDelegate(
  41. (BuildContext context, int index) {
  42. return Container(
  43. alignment: Alignment.center,
  44. color: Colors.lightBlue[100 * (index % 9)],
  45. child: Text('list item $index'),
  46. );
  47. },
  48. ),
  49. ),
  50. ],
  51. ),
  52. ),
  53. );
  54. }
  55. }

运行后的效果为:
Flutter 学习(二十六)可滚动 Widget - 图6

构造函数及参数说明

CustomScrollView 的构造函数为:

  1. class CustomScrollView extends ScrollView {
  2. const CustomScrollView({
  3. Key key,
  4. Axis scrollDirection = Axis.vertical,
  5. bool reverse = false,
  6. ScrollController controller,
  7. bool primary,
  8. ScrollPhysics physics,
  9. bool shrinkWrap = false,
  10. Key center,
  11. double anchor = 0.0,
  12. double cacheExtent,
  13. this.slivers = const <Widget>[],
  14. int semanticChildCount,
  15. DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  16. })
  17. ...
  18. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
scrollDirection Axis 滑动的方向
默认为 Axis.vertical,垂直方向可滑动
可选
reverse bool 控制 CustomScrollView 里列表项的排列顺序,是按照插入顺序排,还是按照插入顺序相反的方向排序。
默认为 false,就是按照插入顺序排序,第一个插入的在头部
,当 reverse 为 true 时,第一个插入的会在底部
可选
controller ScrollController 可以控制 CustomScrollView 滚动的位置
ScrollController 提供以下的几个功能:
1.设置 CustomScrollView 滑动的初始位置
2.可以控制 CustomScrollView 是否存储和恢复滑动的位置
3.可以读取、设置当前滑动的位置
可以继承 ScrollController 实现自定义的功能
当 primary 为 true 时,controller 必须为 null
可选
primary bool 是否是与父级关联的主滚动视图
当为 true 时,即使 CustomScrollView 里没有足够的内容也能滑动
可选
physics ScrollPhysics 设置 CustomScrollView 的滚动效果
值必须为 ScrollPhysics 的子类,比如有如下的值:
AlwaysScrollableScrollPhysics():可以让 CustomScrollView 里没有足够的内容也能滑动
ScrollPhysics():CustomScrollView 在没有足够的内容的时候不能滑动
可选
shrinkWrap bool 是否根据列表项的总长度来设置 CustomScrollView 的长度,默认值为 false。
当 shrinkWrap 为 false 时,CustomScrollView 会在滚动方向扩展到可占用的最大空间
当 shrinkWrap 为 true 时,CustomScrollView 在滚动方向占用的空间就是其列表项的总长度,但是这样会很耗性能,因为当其列表项发生变化时,CustomScrollView 的大小会重新计算
可选
center Key 放在 CustomScrollView 中间的 子Widget 的 key 可选
anchor double CustomScrollView 开始滑动的偏移量
如果 anchor 为 0.0,则 CustomScrollView 的 子Widget 从头开始排列
如果 anchor 为 0.5,则 CustomScrollView 的 子Widget 从中间开始排列
如果 anchor 为 1.0,则 CustomScrollView 的 子Widget 从底部开始排列
可选
cacheExtent double CustomScrollView 可见部分的前面和后面的区域可以用来缓存列表项,
这部分区域的 item 即使不可见,也会加载出来,所以当滑动到这个区域的时候,缓存的区域就会变的可见,
cacheExtent 就表示缓存区域在可见部分的前面和后面有多少像素
可选
slivers List CustomScrollView 的列表项 可选
semanticChildCount int 提供语义信息的列表项的数量
默认为 CustomScrollView 的 item 的数量
可选
dragStartBehavior DragStartBehavior 确定处理拖动开始行为的方式。
如果设置为[DragStartBehavior.start],则在检测到拖动手势时将开始滚动拖动行为
如果设置为[DragStartBehavior.down],它将在首次检测到向下事件时开始
可选

CustomScrollView 的 slivers 属性的值,只能是以 Sliver 开头的一系列 Widget:

  • SliverList
  • SliverFixedExtentList
  • SliverGrid
  • SliverPadding
  • SliverAppBar

GridView

GridView 是一个可以构建二维网格列表的可滚动 Widget。

快速上手

GridView 和 ListView 一样,有五种用法:

使用默认的构造函数,给 children 属性赋值

使用默认构造函数写 GridView,只适用于那些只有少量子 Widget 的 GridView。

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(GridViewDefaultWidget());
  3. class GridViewDefaultWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Test',
  8. home: Scaffold(
  9. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
  10. body: GridView(
  11. gridDelegate:
  12. SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
  13. children: <Widget>[
  14. ListTile(title: Text('Title1')),
  15. ListTile(title: Text('Title2')),
  16. ListTile(title: Text('Title3')),
  17. ListTile(title: Text('Title4')),
  18. ListTile(title: Text('Title5')),
  19. ListTile(title: Text('Title6')),
  20. ListTile(title: Text('Title7')),
  21. ListTile(title: Text('Title8')),
  22. ListTile(title: Text('Title9')),
  23. ListTile(title: Text('Title10')),
  24. ListTile(title: Text('Title11')),
  25. ListTile(title: Text('Title12')),
  26. ListTile(title: Text('Title13')),
  27. ListTile(title: Text('Title14')),
  28. ListTile(title: Text('Title15')),
  29. ListTile(title: Text('Title16')),
  30. ListTile(title: Text('Title17')),
  31. ListTile(title: Text('Title18')),
  32. ListTile(title: Text('Title19')),
  33. ],
  34. )),
  35. );
  36. }
  37. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图7

使用 GridView.count

GridView.count 的定义如下:

  1. GridView.count({
  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 int crossAxisCount,
  11. double mainAxisSpacing = 0.0,
  12. double crossAxisSpacing = 0.0,
  13. double childAspectRatio = 1.0,
  14. bool addAutomaticKeepAlives = true,
  15. bool addRepaintBoundaries = true,
  16. bool addSemanticIndexes = true,
  17. double cacheExtent,
  18. List<Widget> children = const <Widget>[],
  19. int semanticChildCount,
  20. DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  21. })

相比于默认构造函数,其实是将默认构造函数里的 gridDelegate 属性,拆分成了 crossAxisCount、mainAxisSpacing、crossAxisSpacing 和 childAspectRatio。

使用 GridView.count 的 demo 如下:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(GridViewCountWidget());
  3. class GridViewCountWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Test',
  8. home: Scaffold(
  9. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
  10. body: GridView.count(
  11. crossAxisCount: 3,
  12. children: <Widget>[
  13. ListTile(title: Text('Title1')),
  14. ListTile(title: Text('Title2')),
  15. ListTile(title: Text('Title3')),
  16. ListTile(title: Text('Title4')),
  17. ListTile(title: Text('Title5')),
  18. ListTile(title: Text('Title6')),
  19. ListTile(title: Text('Title7')),
  20. ListTile(title: Text('Title8')),
  21. ListTile(title: Text('Title9')),
  22. ListTile(title: Text('Title10')),
  23. ListTile(title: Text('Title11')),
  24. ListTile(title: Text('Title12')),
  25. ListTile(title: Text('Title13')),
  26. ListTile(title: Text('Title14')),
  27. ListTile(title: Text('Title15')),
  28. ListTile(title: Text('Title16')),
  29. ListTile(title: Text('Title17')),
  30. ListTile(title: Text('Title18')),
  31. ListTile(title: Text('Title19')),
  32. ],
  33. )),
  34. );
  35. }
  36. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图8

使用 GridView.extent

GridView.extent 的定义如下:

  1. GridView.extent({
  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 double maxCrossAxisExtent,
  11. double mainAxisSpacing = 0.0,
  12. double crossAxisSpacing = 0.0,
  13. double childAspectRatio = 1.0,
  14. bool addAutomaticKeepAlives = true,
  15. bool addRepaintBoundaries = true,
  16. bool addSemanticIndexes = true,
  17. List<Widget> children = const <Widget>[],
  18. int semanticChildCount,
  19. DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  20. })

这里类似于 GridView.count,因为 GridView.count 相当于 GridView+SliverGridDelegateWithFixedCrossAxisCount,而 GridView.extent 相当于 GridView+SliverGridDelegateWithFixedCrossAxisCount。

和 GridView.count 的布局算法不同。

使用 GridView.extent 的 demo 如下:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(GridViewExtentWidget());
  3. class GridViewExtentWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Test',
  8. home: Scaffold(
  9. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
  10. body: GridView.extent(
  11. maxCrossAxisExtent: 300,
  12. children: <Widget>[
  13. ListTile(title: Text('Title1')),
  14. ListTile(title: Text('Title2')),
  15. ListTile(title: Text('Title3')),
  16. ListTile(title: Text('Title4')),
  17. ListTile(title: Text('Title5')),
  18. ListTile(title: Text('Title6')),
  19. ListTile(title: Text('Title7')),
  20. ListTile(title: Text('Title8')),
  21. ListTile(title: Text('Title9')),
  22. ListTile(title: Text('Title10')),
  23. ListTile(title: Text('Title11')),
  24. ListTile(title: Text('Title12')),
  25. ListTile(title: Text('Title13')),
  26. ListTile(title: Text('Title14')),
  27. ListTile(title: Text('Title15')),
  28. ListTile(title: Text('Title16')),
  29. ListTile(title: Text('Title17')),
  30. ListTile(title: Text('Title18')),
  31. ListTile(title: Text('Title19')),
  32. ],
  33. )),
  34. );
  35. }
  36. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图9

使用 GridView.builder,可用于和数据绑定实现大量或无限的列表

GridView.builder 可以和数据绑定,用于构建大量或无限的列表。而且只会构建那些实际可见的子 Widget。
GridView.builder 的定义如下:

  1. GridView.builder({
  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. @required IndexedWidgetBuilder itemBuilder,
  12. int itemCount,
  13. bool addAutomaticKeepAlives = true,
  14. bool addRepaintBoundaries = true,
  15. bool addSemanticIndexes = true,
  16. double cacheExtent,
  17. int semanticChildCount,
  18. })

多了和 ListView.builder 类似的 itemCount 和 itemBuilder 属性,用法也是一样的:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(GridViewBuilderWidget(
  3. items: List<String>.generate(10000, (i) => "Item $i"),
  4. ));
  5. class GridViewBuilderWidget extends StatelessWidget {
  6. final List<String> items;
  7. GridViewBuilderWidget({Key key, @required this.items}) : super(key: key);
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'Test',
  12. home: Scaffold(
  13. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
  14. body: GridView.builder(
  15. gridDelegate:
  16. SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
  17. itemCount: items.length,
  18. itemBuilder: (context, index) {
  19. return ListTile(
  20. title: Text('${items[index]}'),
  21. );
  22. },
  23. ),
  24. ),
  25. );
  26. }
  27. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图10

使用 GridView.custom

GridView.custom 的定义如下:

  1. const GridView.custom({
  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. @required this.childrenDelegate,
  12. double cacheExtent,
  13. int semanticChildCount,
  14. DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  15. })

增加了 childrenDelegate 的属性,类型为 SliverChildDelegate,具有定制子 Widget 的能力,和 ListView.custom 里的一样,所以用法也一样:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(GridViewCustomWidget(
  3. items: List<String>.generate(10000, (i) => "Item $i"),
  4. ));
  5. class GridViewCustomWidget extends StatelessWidget {
  6. final List<String> items;
  7. GridViewCustomWidget({Key key, @required this.items}) : super(key: key);
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'Test',
  12. home: Scaffold(
  13. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
  14. body: GridView.custom(
  15. gridDelegate:
  16. SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
  17. childrenDelegate: SliverChildListDelegate(<Widget>[
  18. ListTile(title: Text('Title1')),
  19. ListTile(title: Text('Title2')),
  20. ListTile(title: Text('Title3')),
  21. ListTile(title: Text('Title4')),
  22. ListTile(title: Text('Title5')),
  23. ListTile(title: Text('Title6')),
  24. ListTile(title: Text('Title7')),
  25. ListTile(title: Text('Title8')),
  26. ListTile(title: Text('Title9')),
  27. ListTile(title: Text('Title10')),
  28. ListTile(title: Text('Title11')),
  29. ListTile(title: Text('Title12')),
  30. ListTile(title: Text('Title13')),
  31. ListTile(title: Text('Title14')),
  32. ListTile(title: Text('Title15')),
  33. ListTile(title: Text('Title16')),
  34. ListTile(title: Text('Title17')),
  35. ListTile(title: Text('Title18')),
  36. ListTile(title: Text('Title19')),
  37. ]),
  38. ),
  39. ),
  40. );
  41. }
  42. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图11

构造函数及参数说明

GridView 的构造函数,会发现 GridView 的大部分属性都和 ListView 一样:

  1. class GridView extends BoxScrollView {
  2. GridView({
  3. Key key,
  4. Axis scrollDirection = Axis.vertical,
  5. bool reverse = false,
  6. ScrollController controller,
  7. bool primary,
  8. ScrollPhysics physics,
  9. bool shrinkWrap = false,
  10. EdgeInsetsGeometry padding,
  11. @required this.gridDelegate,
  12. bool addAutomaticKeepAlives = true,
  13. bool addRepaintBoundaries = true,
  14. bool addSemanticIndexes = true,
  15. double cacheExtent,
  16. List<Widget> children = const <Widget>[],
  17. int semanticChildCount,
  18. })
  19. ...
  20. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
scrollDirection Axis 滑动的方向
默认为 Axis.vertical,垂直方向可滑动
可选
reverse bool 控制 GridView 里列表项的排列顺序,是按照插入顺序排,还是按照插入顺序相反的方向排序。
默认为 false,就是按照插入顺序排序,第一个插入的在头部
,当 reverse 为 true 时,第一个插入的会在底部
可选
controller ScrollController 可以控制 GridView 滚动的位置
ScrollController 提供以下的几个功能:
1.设置 GridView 滑动的初始位置
2.可以控制 GridView 是否存储和恢复滑动的位置
3.可以读取、设置当前滑动的位置
可以继承 ScrollController 实现自定义的功能
当 primary 为 true 时,controller 必须为 null
可选
primary bool 是否是与父级关联的主滚动视图
当为 true 时,即使 GridView 里没有足够的内容也能滑动
可选
physics ScrollPhysics 设置 GridView 的滚动效果
值必须为 ScrollPhysics 的子类,比如有如下的值:
AlwaysScrollableScrollPhysics():可以让 GridView 里没有足够的内容也能滑动
ScrollPhysics():GridView 在没有足够的内容的时候不能滑动
可选
shrinkWrap bool 是否根据列表项的总长度来设置 GridView 的长度,默认值为 false。
当 shrinkWrap 为 false 时,GridView 会在滚动方向扩展到可占用的最大空间
当 shrinkWrap 为 true 时,GridView 在滚动方向占用的空间就是其列表项的总长度,但是这样会很耗性能,因为当其列表项发生变化时,GridView 的大小会重新计算
可选
padding EdgeInsetsGeometry GridView 的内边距 可选
gridDelegate SliverGridDelegate 控制 GridView 中 子Widget 布局的委托。
SliverGridDelegate 的实现有两个:
SliverGridDelegateWithMaxCrossAxisExtent:横轴 子Widget 为固定长度的布局算法
SliverGridDelegateWithFixedCrossAxisCount:横轴 子Widget 为固定数量的布局算法
必选
addAutomaticKeepAlives bool 是否用 AutomaticKeepAlive 来包列表项,默认为 true
在一个 lazy list 里,如果子 Widget 为了保证自己在滑出可视界面时不被回收,就需要把 addAutomaticKeepAlives 设为 true
当 子Widget 不需要让自己保持存活时,为了提升性能,请把 addAutomaticKeepAlives 设为 false
如果 子Widget 自己维护其 KeepAlive 状态,那么此参数必须置为false。
可选
addRepaintBoundaries bool 是否用 RepaintBoundary 来包列表项,默认为 true
当 addRepaintBoundaries 为 true 时,可以避免列表项重绘,提高性能
但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加 RepaintBoundary 反而会更高效。
可选
addSemanticIndexes bool 是否用 IndexedSemantics 来包列表项,默认为 true
使用 IndexedSemantics 是为了正确的用于辅助模式
可选
cacheExtent double GridView 可见部分的前面和后面的区域可以用来缓存列表项,
这部分区域的 item 即使不可见,也会加载出来,所以当滑动到这个区域的时候,缓存的区域就会变的可见,
cacheExtent 就表示缓存区域在可见部分的前面和后面有多少像素
可选
children List GridView 的列表项 可选
semanticChildCount int 提供语义信息的列表项的数量
默认为 GridView 的 item 的数量
可选

PageView

PageView 是可以一页一页滑动的可滚动 Widget。

使用

PageView 的使用有三种方式:

  1. 使用默认的构造函数
  2. 使用 PageView.builder
  3. 使用 PageView.custom

使用默认的构造函数,给 children 属性赋值

使用默认构造函数写 PageView,只适用于那些只有少量子 Widget 的 PageView。

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(PageViewDefaultWidget());
  3. class PageViewDefaultWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Test',
  8. home: Scaffold(
  9. appBar:
  10. AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
  11. body: PageView(
  12. onPageChanged: (index){
  13. print('current page $index ');
  14. },
  15. children: <Widget>[
  16. ListTile(title: Text('Title0')),
  17. ListTile(title: Text('Title1')),
  18. ListTile(title: Text('Title2')),
  19. ListTile(title: Text('Title3')),
  20. ListTile(title: Text('Title4')),
  21. ],
  22. )),
  23. );
  24. }
  25. }

运行效果为:
Flutter 学习(二十六)可滚动 Widget - 图12
可以左右滑动切换页面。

使用 PageView.builder,可用于和数据绑定实现大量或无限的列表

PageView.builder 可以和数据绑定,用于构建大量或无限的列表。而且只会构建那些实际可见的子 Widget。

  1. PageView.builder({
  2. Key key,
  3. this.scrollDirection = Axis.horizontal,
  4. this.reverse = false,
  5. PageController controller,
  6. this.physics,
  7. this.pageSnapping = true,
  8. this.onPageChanged,
  9. @required IndexedWidgetBuilder itemBuilder,
  10. int itemCount,
  11. this.dragStartBehavior = DragStartBehavior.down,
  12. })

多了和 ListView.builder 类似的 itemCount 和 itemBuilder 属性,用法也是一样的:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(PageViewBuilderWidget(
  3. items: List<String>.generate(10000, (i) => "Item $i"),
  4. ));
  5. class PageViewBuilderWidget extends StatelessWidget {
  6. final List<String> items;
  7. PageViewBuilderWidget({Key key, @required this.items}) : super(key: key);
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'Test',
  12. home: Scaffold(
  13. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
  14. body: PageView.builder(
  15. onPageChanged: (index) {
  16. print('current page $index ');
  17. },
  18. itemCount: items.length,
  19. itemBuilder: (context, index) {
  20. return ListTile(
  21. title: Text('${items[index]}'),
  22. );
  23. },
  24. ),
  25. ),
  26. );
  27. }
  28. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图13
可以左右滑动切换页面。

使用 PageView.custom

PageView.custom 的定义如下:

  1. PageView.custom({
  2. Key key,
  3. this.scrollDirection = Axis.horizontal,
  4. this.reverse = false,
  5. PageController controller,
  6. this.physics,
  7. this.pageSnapping = true,
  8. this.onPageChanged,
  9. @required this.childrenDelegate,
  10. this.dragStartBehavior = DragStartBehavior.down,
  11. })

增加了 childrenDelegate 的属性,类型为 SliverChildDelegate,具有定制子 Widget 的能力,和 ListView.custom 里的一样,所以用法也一样:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(PageViewCustomWidget());
  3. class PageViewCustomWidget extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Test',
  8. home: Scaffold(
  9. appBar: AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
  10. body: PageView.custom(
  11. onPageChanged: (index) {
  12. print('current page $index ');
  13. },
  14. childrenDelegate: SliverChildListDelegate(<Widget>[
  15. ListTile(title: Text('Title0')),
  16. ListTile(title: Text('Title1')),
  17. ListTile(title: Text('Title2')),
  18. ListTile(title: Text('Title3')),
  19. ListTile(title: Text('Title4')),
  20. ]),
  21. )),
  22. );
  23. }
  24. }

运行效果如下:
Flutter 学习(二十六)可滚动 Widget - 图14
可以左右滑动切换页面。

构造函数及参数说明

先看 PageView 的构造函数:

  1. class PageView extends StatefulWidget {
  2. PageView({
  3. Key key,
  4. this.scrollDirection = Axis.horizontal,
  5. this.reverse = false,
  6. PageController controller,
  7. this.physics,
  8. this.pageSnapping = true,
  9. this.onPageChanged,
  10. List<Widget> children = const <Widget>[],
  11. this.dragStartBehavior = DragStartBehavior.down,
  12. }) :
  13. ...
  14. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
scrollDirection Axis 滑动的方向
默认为 Axis.vertical,垂直方向可滑动
可选
reverse bool 控制 PageView 里列表项的排列顺序,是按照插入顺序排,还是按照插入顺序相反的方向排序。
默认为 false,就是按照插入顺序排序,第一个插入的在头部
,当 reverse 为 true 时,第一个插入的会在底部
可选
controller PageController PageController 可以控制滑动到哪一页,还有其他功能 可选
physics ScrollPhysics 设置 PageView 的滚动效果
应该使用 PageScrollPhysics
可选
pageSnapping bool 默认值为 false
设置为false以禁用页面捕捉,对自定义滚动行为很有用。
可选
onPageChanged ValueChanged 当 PageView 当前页面切换的时候调用 可选
children List PageView 的列表项 可选
semanticChildCount int 提供语义信息的列表项的数量
默认为 PageView 的 item 的数量
可选
dragStartBehavior DragStartBehavior 确定处理拖动开始行为的方式。
如果设置为[DragStartBehavior.start],则在检测到拖动手势时将开始滚动拖动行为
如果设置为[DragStartBehavior.down],它将在首次检测到向下事件时开始
可选

参考

【1】Flutter 实战
【2】Flutter 中文文档
【3】Flutter 完全手册