GridView
GridView可以构建一个二维网格列表,其默认构造函数:
GridView({
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required SliverGridDelegate gridDelegate, //控制子widget layout的委托
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
})
gridDelegate
: 类型是SliverGridDelegate
,用来控制GridView子组件如何排列(layout)。SliverGridDelegate
是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法。SliverGridDelegate
有两个子类:- SliverGridDelegateWithFixedCrossAxisCount
- SliverGridDelegateWithMaxCrossAxisExtent
SliverGridDelegateWithFixedCrossAxisCount
实现了一个横轴为固定数量子元素的layout算法,其构造函数为:
SliverGridDelegateWithFixedCrossAxisCount({
@required double crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
crossAxisCount
:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商。mainAxisSpacing
:主轴方向的间距。crossAxisSpacing
:横轴方向子元素的间距。childAspectRatio
:子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度。
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //横轴三个子widget
childAspectRatio: 1.0 //宽高比为1时,子widget
),
children:<Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast)
]
);
GridView.count
GridView.count
构造函数内部使用了SliverGridDelegateWithFixedCrossAxisCount
,我们通过它可以快速的创建横轴固定数量子元素的GridView。如果使用GridView.count
,上面的例子就是:
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
SliverGridDelegateWithMaxCrossAxisExtent
实现了一个横轴子元素为固定最大长度的layout算法,其构造函数为:
SliverGridDelegateWithMaxCrossAxisExtent({
double maxCrossAxisExtent,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
maxCrossAxisExtent
: 为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的。
举个例子,如果ViewPort的横轴长度是450,那么当
maxCrossAxisExtent
的值在区间[450/4,450/3)内的话,子元素最终实际长度都为450/4=112.5。这很好理解,因为如果实际长度大于112.5,那么横轴方向只能放3个,放4个是放不下的,放完3个后肯定有剩余空间,那就不能平分了。
childAspectRatio
: 子元素横轴和主轴的长度比为最终的长度比。其它参数和SliverGridDelegateWithFixedCrossAxisCount相同。
GridView(
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0 //宽高比为2
),
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
GridView.extent
和GridView.count
对应,GridView.extent
构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent
,通过它可以快速的创建纵轴子元素为固定最大长度的的GridView。如果使用GridView.extent
,上面的例子等价于:
GridView.extent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0,
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
GridView.builder
不管是使用GridView默认构造函数,还是使用``GridView.count或
GridView.extent```,都需要一个children作为参数。这些方式都会提前将所有子widget都构建好,所以只适用于子widget数量比较少时,当子widget比较多时,我们可以通过GridView.builder来动态创建子widget。
GridView.builder(
...
@required SliverGridDelegate gridDelegate,
@required IndexedWidgetBuilder itemBuilder, //子widget构建器
)
示例
从一个异步数据源(如网络)分批获取一些Icon,然后用GridView来展示:
class InfiniteGridViewRoute extends StatefulWidget {
const InfiniteGridViewRoute({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return new _InfiniteGridViewRouteState();
}
}
class _InfiniteGridViewRouteState extends State<InfiniteGridViewRoute> {
List<IconData> _icons = []; //保存Icon数据
@override
void initState() {
super.initState();
_retrieveIcons();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GridView demo"),
),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //每行三列
childAspectRatio: 1.0 //显示区域宽高相等
),
itemCount: _icons.length,
itemBuilder: (context, index) {
//如果显示到最后一个并且Icon总数小于200时继续获取数据
if (index == _icons.length - 1 && _icons.length < 200) {
_retrieveIcons();
return Container(
padding: const EdgeInsets.all(20),
alignment: Alignment.center,
child: SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(strokeWidth: 2.0)),
);
}
return Icon(_icons[index]);
}));
}
//模拟异步获取数据
void _retrieveIcons() {
Future.delayed(Duration(seconds: 2)).then((e) {
_icons.addAll([
Icons.ac_unit,
Icons.airport_shuttle,
Icons.all_inclusive,
Icons.beach_access,
Icons.cake,
Icons.free_breakfast
]);
setState(() {
//重新构建列表
});
});
}
}
CustomScrollView
CustomScrollView
是可以使用Sliver来自定义滚动模型(效果)的组件。
它可以包含多种滚动模型,举个例子,假设有一个页面,顶部需要一个GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体。如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的,所以这时就需要一个"胶水",把这些彼此独立的可滚动组件"粘"起来,而CustomScrollView
的功能就相当于“胶水”。
可滚动组件的Sliver版
Sliver: 在前面讲过,有细片、薄片之意,在Flutter中,Sliver通常指可滚动组件子元素(就像一个个薄片一样)。
在CustomScrollView中,需要粘起来的可滚动组件就是CustomScrollView
的Sliver了,如果直接将ListView、GridView作为CustomScrollView是不行的,因为它们本身是可滚动组件而并不是Sliver!
通常,为了能让可滚动组件能和CustomScrollView
配合使用,实现了统一的滑动效果,Flutter提供了一些可滚动组件的Sliver版,如SliverList、SliverGrid等。也就是:CustomScrollView
+SliverList
、SliverGrid
…
实际上Sliver版的可滚动组件和非Sliver版的可滚动组件最大的区别就是前者不包含滚动模型(自身不能再滚动),而后者包含滚动模型 。
上面之所以说“大多数”Sliver都和可滚动组件对应,是由于还有一些如SliverPadding、SliverAppBar等是和可滚动组件无关的,它们主要是为了结合CustomScrollView一起使用,这是因为CustomScrollView的子组件必须都是Sliver。
示例
class CustomScrollViewTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
//因为本路由没有使用Scaffold,为了让子级Widget(如Text)使用
//Material Design 默认的样式风格,我们使用Material作为本路由的根。
return Material(
child: CustomScrollView(
slivers: <Widget>[
//AppBar,包含一个导航栏
SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Demo'),
background: Image.asset(
"./images/avatar.png", fit: BoxFit.cover,),
),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: new SliverGrid( //Grid
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //Grid按两列显示
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建子widget
return new Container(
alignment: Alignment.center,
color: Colors.cyan[100 * (index % 9)],
child: new Text('grid item $index'),
);
},
childCount: 20,
),
),
),
//List
new SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建列表项
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 50 //50个列表项
),
),
],
),
);
}
}
代码分为三部分:
- 头部SliverAppBar:SliverAppBar对应AppBar,两者不同之处在于SliverAppBar可以集成到CustomScrollView。SliverAppBar可以结合FlexibleSpaceBar实现Material Design中头部伸缩的模型
- 中间的SliverGrid:它用SliverPadding包裹以给SliverGrid添加补白。
- SliverGrid是一个两列,宽高比为4的网格,它有20个子组件。
- 底部SliverFixedExtentList:它是一个所有子元素高度都为50像素的列表。