在Flutter中,有很多Widgets,这里,介绍几个关于列表和表单的Widgets。
ListView
ListView 是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持基于Sliver的延迟构建模型。
默认构造函数
默认构造函数有一个children函数,它接受一个Widget列表。这种方式适合只有少量子组件的情况,不基于Sliver的懒加载模型。
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: [
const Text("1"),
const Text("2"),
const Text("3"),
const Text("4")
],
),
ListView.builder
ListView.builder适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的。
body:ListView.builder(
// 列表项的数量,如果为null,则为无限列表
itemCount: 100,
// 列表项高度
itemExtent: 50,
// 列表项的构建器
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('$index'),
);
},
),
ListView.separated
ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器
body: ListView.separated(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('$index'),
);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
color: Colors.red
);
},
itemCount: 100,
),
ListView.custom
如果需要自定义列表项生成模型,可以通过ListView.custom来自定义,它需要实现一个SliverChildDelegate用来给ListView生成列表项组件.
Widget listViewLayoutCustom() {
return ListView.custom(
itemExtent: 40.0,
childrenDelegate: MyChildrenDelegate(
(BuildContext context, int i) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
"name",
style: TextStyle(fontSize: 18.0, color: Colors.red)
),
Text(
"age",
style: TextStyle(fontSize: 18.0, color: Colors.green)
),
Text(
"text",
style: TextStyle(fontSize: 18.0, color: Colors.blue)
),
],
));
},
childCount: 20,
),
cacheExtent: 0.0,
);
}
/*
* 继承SliverChildBuilderDelegate 可以对列表的监听
*/
class MyChildrenDelegate extends SliverChildBuilderDelegate {
MyChildrenDelegate(
Widget Function(BuildContext, int) builder, {
int childCount,
bool addAutomaticKeepAlive = true,
bool addRepaintBoundaries = true,
}) : super(builder,
childCount: childCount,
addAutomaticKeepAlives: addAutomaticKeepAlive,
addRepaintBoundaries: addRepaintBoundaries);
///监听 在可见的列表中 显示的第一个位置和最后一个位置
@override
void didFinishLayout(int firstIndex, int lastIndex) {
print('firstIndex: $firstIndex, lastIndex: $lastIndex');
}
///可不重写 重写不能为null 默认是true 添加进来的实例与之前的实例是否相同 相同返回true 反之false
///listView 暂时没有看到应用场景 源码中使用在 SliverFillViewport 中
@override
bool shouldRebuild(SliverChildBuilderDelegate oldDelegate) {
print("oldDelegate$oldDelegate");
return super.shouldRebuild(oldDelegate);
}
}
GridView
GridView 可以构建一个二维网格列表,它默认构造函数与ListView有很多一样的地方,我们唯一需要关注的是gridDelegate这个参数,是SliverGridDelegate类型,用来控制子组件如何排列(layout)。
SliverGridDelegate 是一个抽象类,定义了GridView layout相关接口,子类需要通过实现它们来实现具体的布局。在Flutter中,SliverGridDelegate有两个子类,SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent。
SliverGridDelegateWithFixedCrossAxisCount
该子类实现了一个横轴为固定数量子元素的layout算法,其内部属性:
字段 | 作用 |
---|---|
crossAxisCount | 横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商 |
mainAxisSpacing | 主轴方向的间距 |
crossAxisSpacing | 横轴方向子元素的间距 |
childAspectRatio | 子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度 |
GridView.count
GridView.count 内部实现了SliverGridDelegateWithFixedCrossAxisCount,我们通过它可以快速创建横轴固定数量资源的GridView。
比较一下两种方式(同一个效果):
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, childAspectRatio: 1.0),
children: [
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.face)
],
);
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
children: [
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.face)
],
);
SliverGridDelegateWithMaxCrossAxisExtent
该子类实现了一个横轴子元素为固定最大长度的layout算法,内部属性:
字段 | 作用 |
---|---|
maxCrossAxisExtent | 子元素在横轴上的最大长度 |
mainAxisSpacing | 主轴方向的间距 |
crossAxisSpacing | 横轴方向子元素的间距 |
childAspectRatio | 子元素在横轴长度和主轴长度的最终的长度比 |
maxCrossAxisExtent之所以是最大长度,是因为横轴方向每个子元素的长度仍然是等分的。比如横轴长度为450,当maxCrossAxisExtent值在[450/4,450/3)之间的话,子元素的最终实际长度都为112.5。
GridView.extent
GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们可以通过它快速创建。
比较一下两种方式(同一种效果)
GridView(
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120, childAspectRatio: 2.0),
children: [
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.face)
],
);
GridView.extent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0,
children: [
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.face)
],
);
GridView.builder
以上几种方式都需要一个widget数组作为子元素,这些方式都会提前将所有子元素构建好,所以只适用于widget数量较少的情况。当子widget比较多时,我们可以通过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,
})
其中,gridDelegate和itemBuilder是必须实现的。
gridDelegateh是SliverGridDelegate类型,itemBuilder为子widget构建器。
import 'package:flutter/material.dart';
class GridViewRoute extends StatefulWidget {
@override
State createState() {
return _GridViewRouteState();
}
}
class _GridViewRouteState extends State {
List _icons = [];
@override
void initState() {
_retrieveIcons();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GridViewRoute'),
),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, childAspectRatio: 1.0),
itemCount: _icons.length,
itemBuilder: (context, index) {
if (index == _icons.length - 1 && _icons.length < 200) {
_retrieveIcons();
}
return Icon(_icons[index]);
}));
}
// 异步获取数据
void _retrieveIcons() {
Future.delayed(Duration(milliseconds: 200)).then((e) {
setState(() {
_icons.addAll([
Icons.ac_unit,
Icons.airport_shuttle,
Icons.all_inclusive,
Icons.beach_access,
Icons.cake,
Icons.free_breakfast
]);
});
});
}
}