前面介绍的 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,例如:
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[Text('Hello Flutter ' * 100)],
),
)
可以将前面子 Widget 超过父容器时出现 overflowed 错误的代码更改为:
import 'package:flutter/material.dart';
main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(title: new Text('Flutter 可滚动Widget --SingleChildScrollView')),
body: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[Text('Hello Flutter ' * 100)],
),
)));
}
}
运行效果为:
这里的文本内容没有了 overflowed 错误的黄黑色的条,而且可以在水平方向上滑动。
构造函数及参数说明
先看 SingleChildScrollView 的构造函数:
class SingleChildScrollView extends StatelessWidget {
const SingleChildScrollView({
Key key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.padding,
bool primary,
this.physics,
this.controller,
this.child,
this.dragStartBehavior = DragStartBehavior.down,
})
...
}
参数名字 | 参数类型 | 意义 | 必选 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 的使用,例如:
- ListTile:一个固定高度的行,通常包含一些文本,以及一个行前或行尾图标。
- Stepper:一个 Material Design 步骤指示器,显示一系列步骤的过程
- Divider:一个逻辑 1 像素厚的水平分割线,两边都有填充
快速上手
有四种使用 ListView 的方法:
使用默认的构造函数,给 children 属性赋值
**
使用默认构造函数写 ListView,需要给 children 属性赋值,但只适用于那些只有少量子 Widget 的 ListView,ListView 创建的时候,其子 Widget 也会一起创建。
Demo 如下:
import 'package:flutter/material.dart';
main() => runApp(new ListViewDefaultWidget());
class ListViewDefaultWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar:
new AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
body: ListView(
children: <Widget>[
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
ListTile(title: Text('Title5')),
ListTile(title: Text('Title6')),
ListTile(title: Text('Title7')),
ListTile(title: Text('Title8')),
ListTile(title: Text('Title9')),
ListTile(title: Text('Title10')),
ListTile(title: Text('Title11')),
ListTile(title: Text('Title12')),
ListTile(title: Text('Title13')),
ListTile(title: Text('Title14')),
ListTile(title: Text('Title15')),
ListTile(title: Text('Title16')),
ListTile(title: Text('Title17')),
ListTile(title: Text('Title18')),
ListTile(title: Text('Title19')),
],
)));
}
}
运行效果为:
使用 ListView.builder,可用于和数据绑定实现大量或无限的列表
ListView.builder 可以用于构建大量或无限的列表,是因为 ListView.builder 只会构建那些实际可见的子 Widget。
ListView.builder 的定义为:
ListView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
...
大部分属性都和 ListView 的默认构造函数一样,除了这两个:
- int itemCount
代表子 Widget 的数量,虽然是可选的,但是还是建议赋值,可以让 ListView 预估最大滑动距离,从而提升性能。如果为 null,则子节点数由 [itemBuilder] 返回 null 的最小索引确定。 - @required IndexedWidgetBuilder itemBuilder
itemBuilder 用于创建实际可见的子 Widget,只有索引大于或等于零且小于 itemCount 才会调用 itemBuilder。
下面写一个数据和 ListView.builder 绑定使用的例子:
import 'package:flutter/material.dart';
void main() => runApp(ListViewBuilderWidget(
items: List<String>.generate(10000, (i) => "Item $i"),
));
class ListViewBuilderWidget extends StatelessWidget {
final List<String> items;
ListViewBuilderWidget({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index]}'),
);
},
),
),
);
}
}
运行后的效果为:
要实现一个无限循环列表,只要不给 itemCount 赋值就行,如下:
ListView.builder(
padding: EdgeInsets.all(8.0),
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Title $index'),);
},
)
使用 ListView.separated,具有分割项的 ListView.builder
看 ListView.separated 的定义:
ListView.separated({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required IndexedWidgetBuilder itemBuilder,
@required IndexedWidgetBuilder separatorBuilder,
@required int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
})
...
相比 ListView.builder 多了一个 separatorBuilder,separatorBuilder 就是用于构建分割项的,而且 itemBuilder、separatorBuilder、itemCount 都是必选的。
使用的 demo 如下:
import 'package:flutter/material.dart';
void main() => runApp(ListViewSeparatedWidget(
items: List<String>.generate(10000, (i) => "Item $i"),
));
class ListViewSeparatedWidget extends StatelessWidget {
final List<String> items;
ListViewSeparatedWidget({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
body: ListView.separated(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index]}'),
);
},
separatorBuilder: (context, index) {
return Container(
constraints: BoxConstraints.tightFor(height: 10),
color: Colors.orange,
);
},
),
),
);
}
}
使用 ListView.custom,需要使用 SliverChildDelegate
SliverChildDelegate 提供了定制子 Widget 的能力。
首先看 ListView.custom 的定义:
const ListView.custom({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required this.childrenDelegate,
double cacheExtent,
int semanticChildCount,
})
childrenDelegate 为必选参数,在看如何实现 SliverChildDelegate,发现 SliverChildDelegate 是一个抽象类,SliverChildDelegate 的 build 方法可以对单个子 Widget 进行自定义处理,而且 SliverChildDelegate 有个默认实现 SliverChildListDelegate,所以我们用 SliverChildListDelegate 来实现 ListView.custom,代码如下:
import 'package:flutter/material.dart';
void main() => runApp(ListViewCustomWidget(
items: List<String>.generate(10000, (i) => "Item $i"),
));
class ListViewCustomWidget extends StatelessWidget {
final List<String> items;
ListViewCustomWidget({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- ListView')),
body: ListView.custom(
childrenDelegate: SliverChildListDelegate(<Widget>[
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
ListTile(title: Text('Title5')),
ListTile(title: Text('Title6')),
ListTile(title: Text('Title7')),
ListTile(title: Text('Title8')),
ListTile(title: Text('Title9')),
ListTile(title: Text('Title10')),
ListTile(title: Text('Title11')),
ListTile(title: Text('Title12')),
ListTile(title: Text('Title13')),
ListTile(title: Text('Title14')),
ListTile(title: Text('Title15')),
ListTile(title: Text('Title16')),
ListTile(title: Text('Title17')),
ListTile(title: Text('Title18')),
ListTile(title: Text('Title19')),
]),
),
),
);
}
}
构造函数及参数使用
首先看 ListView 的构造函数:
class ListView extends BoxScrollView {
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
...
}
参数名字 | 参数类型 | 意义 | 必选 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 一样的功能,所以:
- CustomScrollView 不像 SingleChildScrollView 一样只能包含一个子 Widget,
- CustomScrollView 可以实现比 ListView 更复杂的滑动效果,例如:吸顶,所以当 ListView 不能实现一些滑动效果时,就应该使用 CustomScrollView,但是如果没有特殊的效果,而是数据展示,就使用 ListView。
使用
CustomScrollView 里面可以添加多个 Widget,而且可以为 Widget 提供复杂的滑动效果,需要为其 slivers 参数赋值,而且 slivers 参数只能接受特定的 Widget,例如:
CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('grid item $index'),
);
},
childCount: 20,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('list item $index'),
);
},
),
),
],
)
CustomScrollView 在一个页面使用的 Demo 为:
import 'package:flutter/material.dart';
void main() => runApp(CustomScrollViewWidget());
class CustomScrollViewWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar:
AppBar(title: new Text('Flutter 可滚动Widget -- CustomScrollView')),
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('grid item $index'),
);
},
childCount: 20,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('list item $index'),
);
},
),
),
],
),
),
);
}
}
构造函数及参数说明
CustomScrollView 的构造函数为:
class CustomScrollView extends ScrollView {
const CustomScrollView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
Key center,
double anchor = 0.0,
double cacheExtent,
this.slivers = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
...
}
参数名字 | 参数类型 | 意义 | 必选 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。
import 'package:flutter/material.dart';
void main() => runApp(GridViewDefaultWidget());
class GridViewDefaultWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
body: GridView(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
children: <Widget>[
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
ListTile(title: Text('Title5')),
ListTile(title: Text('Title6')),
ListTile(title: Text('Title7')),
ListTile(title: Text('Title8')),
ListTile(title: Text('Title9')),
ListTile(title: Text('Title10')),
ListTile(title: Text('Title11')),
ListTile(title: Text('Title12')),
ListTile(title: Text('Title13')),
ListTile(title: Text('Title14')),
ListTile(title: Text('Title15')),
ListTile(title: Text('Title16')),
ListTile(title: Text('Title17')),
ListTile(title: Text('Title18')),
ListTile(title: Text('Title19')),
],
)),
);
}
}
使用 GridView.count
GridView.count 的定义如下:
GridView.count({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry 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,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
相比于默认构造函数,其实是将默认构造函数里的 gridDelegate 属性,拆分成了 crossAxisCount、mainAxisSpacing、crossAxisSpacing 和 childAspectRatio。
使用 GridView.count 的 demo 如下:
import 'package:flutter/material.dart';
void main() => runApp(GridViewCountWidget());
class GridViewCountWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
body: GridView.count(
crossAxisCount: 3,
children: <Widget>[
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
ListTile(title: Text('Title5')),
ListTile(title: Text('Title6')),
ListTile(title: Text('Title7')),
ListTile(title: Text('Title8')),
ListTile(title: Text('Title9')),
ListTile(title: Text('Title10')),
ListTile(title: Text('Title11')),
ListTile(title: Text('Title12')),
ListTile(title: Text('Title13')),
ListTile(title: Text('Title14')),
ListTile(title: Text('Title15')),
ListTile(title: Text('Title16')),
ListTile(title: Text('Title17')),
ListTile(title: Text('Title18')),
ListTile(title: Text('Title19')),
],
)),
);
}
}
使用 GridView.extent
GridView.extent 的定义如下:
GridView.extent({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry 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,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
这里类似于 GridView.count,因为 GridView.count 相当于 GridView+SliverGridDelegateWithFixedCrossAxisCount,而 GridView.extent 相当于 GridView+SliverGridDelegateWithFixedCrossAxisCount。
和 GridView.count 的布局算法不同。
使用 GridView.extent 的 demo 如下:
import 'package:flutter/material.dart';
void main() => runApp(GridViewExtentWidget());
class GridViewExtentWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
body: GridView.extent(
maxCrossAxisExtent: 300,
children: <Widget>[
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
ListTile(title: Text('Title5')),
ListTile(title: Text('Title6')),
ListTile(title: Text('Title7')),
ListTile(title: Text('Title8')),
ListTile(title: Text('Title9')),
ListTile(title: Text('Title10')),
ListTile(title: Text('Title11')),
ListTile(title: Text('Title12')),
ListTile(title: Text('Title13')),
ListTile(title: Text('Title14')),
ListTile(title: Text('Title15')),
ListTile(title: Text('Title16')),
ListTile(title: Text('Title17')),
ListTile(title: Text('Title18')),
ListTile(title: Text('Title19')),
],
)),
);
}
}
使用 GridView.builder,可用于和数据绑定实现大量或无限的列表
GridView.builder 可以和数据绑定,用于构建大量或无限的列表。而且只会构建那些实际可见的子 Widget。
GridView.builder 的定义如下:
GridView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
})
多了和 ListView.builder 类似的 itemCount 和 itemBuilder 属性,用法也是一样的:
import 'package:flutter/material.dart';
void main() => runApp(GridViewBuilderWidget(
items: List<String>.generate(10000, (i) => "Item $i"),
));
class GridViewBuilderWidget extends StatelessWidget {
final List<String> items;
GridViewBuilderWidget({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
body: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index]}'),
);
},
),
),
);
}
}
使用 GridView.custom
GridView.custom 的定义如下:
const GridView.custom({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required this.childrenDelegate,
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
增加了 childrenDelegate 的属性,类型为 SliverChildDelegate,具有定制子 Widget 的能力,和 ListView.custom 里的一样,所以用法也一样:
import 'package:flutter/material.dart';
void main() => runApp(GridViewCustomWidget(
items: List<String>.generate(10000, (i) => "Item $i"),
));
class GridViewCustomWidget extends StatelessWidget {
final List<String> items;
GridViewCustomWidget({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
body: GridView.custom(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
childrenDelegate: SliverChildListDelegate(<Widget>[
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
ListTile(title: Text('Title5')),
ListTile(title: Text('Title6')),
ListTile(title: Text('Title7')),
ListTile(title: Text('Title8')),
ListTile(title: Text('Title9')),
ListTile(title: Text('Title10')),
ListTile(title: Text('Title11')),
ListTile(title: Text('Title12')),
ListTile(title: Text('Title13')),
ListTile(title: Text('Title14')),
ListTile(title: Text('Title15')),
ListTile(title: Text('Title16')),
ListTile(title: Text('Title17')),
ListTile(title: Text('Title18')),
ListTile(title: Text('Title19')),
]),
),
),
);
}
}
构造函数及参数说明
GridView 的构造函数,会发现 GridView 的大部分属性都和 ListView 一样:
class GridView extends BoxScrollView {
GridView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
})
...
}
参数名字 | 参数类型 | 意义 | 必选 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 的使用有三种方式:
- 使用默认的构造函数
- 使用 PageView.builder
- 使用 PageView.custom
使用默认的构造函数,给 children 属性赋值
使用默认构造函数写 PageView,只适用于那些只有少量子 Widget 的 PageView。
import 'package:flutter/material.dart';
void main() => runApp(PageViewDefaultWidget());
class PageViewDefaultWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar:
AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
body: PageView(
onPageChanged: (index){
print('current page $index ');
},
children: <Widget>[
ListTile(title: Text('Title0')),
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
],
)),
);
}
}
运行效果为:
可以左右滑动切换页面。
使用 PageView.builder,可用于和数据绑定实现大量或无限的列表
PageView.builder 可以和数据绑定,用于构建大量或无限的列表。而且只会构建那些实际可见的子 Widget。
PageView.builder({
Key key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
PageController controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
this.dragStartBehavior = DragStartBehavior.down,
})
多了和 ListView.builder 类似的 itemCount 和 itemBuilder 属性,用法也是一样的:
import 'package:flutter/material.dart';
void main() => runApp(PageViewBuilderWidget(
items: List<String>.generate(10000, (i) => "Item $i"),
));
class PageViewBuilderWidget extends StatelessWidget {
final List<String> items;
PageViewBuilderWidget({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
body: PageView.builder(
onPageChanged: (index) {
print('current page $index ');
},
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index]}'),
);
},
),
),
);
}
}
运行效果如下:
可以左右滑动切换页面。
使用 PageView.custom
PageView.custom 的定义如下:
PageView.custom({
Key key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
PageController controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
@required this.childrenDelegate,
this.dragStartBehavior = DragStartBehavior.down,
})
增加了 childrenDelegate 的属性,类型为 SliverChildDelegate,具有定制子 Widget 的能力,和 ListView.custom 里的一样,所以用法也一样:
import 'package:flutter/material.dart';
void main() => runApp(PageViewCustomWidget());
class PageViewCustomWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
body: PageView.custom(
onPageChanged: (index) {
print('current page $index ');
},
childrenDelegate: SliverChildListDelegate(<Widget>[
ListTile(title: Text('Title0')),
ListTile(title: Text('Title1')),
ListTile(title: Text('Title2')),
ListTile(title: Text('Title3')),
ListTile(title: Text('Title4')),
]),
)),
);
}
}
运行效果如下:
可以左右滑动切换页面。
构造函数及参数说明
先看 PageView 的构造函数:
class PageView extends StatefulWidget {
PageView({
Key key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
PageController controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
List<Widget> children = const <Widget>[],
this.dragStartBehavior = DragStartBehavior.down,
}) :
...
}
参数名字 | 参数类型 | 意义 | 必选 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 完全手册