ListView
在Flutter中使用ListView来实现一个列表,类似于iOS中的UITableView或者UICollectionView。与iOS不同的是,ListView需要传递一个widget列表,Flutter会保证它的流畅度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class _ListViewSampleAppPageState extends State<ListViewSampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List View'),
),
body: ListView(
children: _getListData(),
),
);
}
List<Widget> _getListData() {
List<Widget> widgets = [];
for (int i = 0; i< 100; i++) {
widgets.add(
Padding(padding: EdgeInsets.all(10), child: Text('Row at index: $i'),)
);
}
return widgets;
}
}
ListView默认是没有分割线的。
在Flutter中采用传递进来的widget的touch handle
来处理点击事件,该事件可以获取点击的元素位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Widget> _getListData() {
List<Widget> widgets = [];
for (int i = 0; i< 100; i++) {
widgets.add(
GestureDetector(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('Row at index: $i'),
),
onTap: () {
print('row tapped');
},
)
);
}
return widgets;
}
Flutter中可以通过setState()来更新ListView,当前后的ListView相同时,不会更新。最简单的方法就是在setState()方法中创建一个新的list,并把旧list的数据拷贝给新的list,这种做法仅限于数据量很小时。当数据量大时,可以使用ListView.Builder
来构建列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class _ListViewBuilderSampleAppPageState extends State<ListViewBuilderSampleAppPage> {
List widgets = [];
@override
void initState() {
// TODO: implement initState
super.initState();
_getWidgets();
}
_getWidgets() {
for (var i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('row at inde: $i'),
),
onTap: () {
// 点击时新增一个列表子元素
setState(() {
widgets.add(
getRow(widgets.length + 1)
);
print('tap row at $i');
});
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Builder'),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}
),
);
}
}
使用ListView.builder有两个主要参数,一个是指定列表初始化长度的itemCount,另一个是返回每一个位置上item的itemBuilder,分别对应iOS中的以下方法:
1
2
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
当希望两个列表子元素间有分割时,可以使用ListView.separated,相比ListView.builder,多了一个主要参数separatorBuilder,它主要用来构建例如分割线等子Widget。它一般用于具有固定数量的子控件的列表视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List View SeparatorBuilder'),
),
body: ListView.separated(
itemCount: widgets.length,
itemBuilder: (BuildContext content, int position) {
return widgets[position];
},
separatorBuilder: (BuildContext context, int position) {
// 分割线
// return Container(height: 0.5, color: Colors.grey[300], );
// indent 左侧缩进宽度
return Divider(height: 1.0, indent: 10, color: Colors.grey[300]);
// 不带child Widget 的装饰容盒子,只使用边框参数
// return DecoratedBox( decoration: BoxDecoration(border: Border.all(color: Colors.red, width: 0.5),));
},
),
);
}
GridView
GridView可以构建一个二维网格列表,它更类似iOS中的UICollectionView,它有一个必须参数gridDelegate,它是控制子widget layout的委托,用来控制子控件布局,每个子元素的大小默认是相同的。gridDelegate的类型是抽象类SliverGridDelegate,其中定义了GridView Layout相关接口,子类实现具体的布局算法必,Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent。
SliverGridDelegateWithFixedCrossAxisCount类用来提供纵轴方向的item的属性,四个主要参数如下:
- crossAxisCount, 纵轴子元素数量
- mainAxisSpacing, 主轴方向子元素间距
- crossAxisSpacing, 纵轴方向子元素的间距
- childAspectRatio, 宽度与高度的比例,例如:1.0代表宽高相等,2.0代表高度为宽度的一半,依次类推,它和crossAxisCount基本确定了子元素的大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
lass _GridViewSampleAppPageState extends State<GridViewSampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GridView'),
),
body: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //纵轴子元素数量
mainAxisSpacing: 10.0, // 主轴方向子元素间距
crossAxisSpacing: 10.0, // 纵轴方向子元素的间距
childAspectRatio: 1.0 // 宽度与高度的比例,例如:1.0代表宽高相等,2.0代表高度为宽度的一半,依次类推
),
children: <Widget>[
Icon(Icons.add),
Icon(Icons.update),
Icon(Icons.edit),
Icon(Icons.favorite),
Icon(Icons.face),
FlutterLogo(style: FlutterLogoStyle.horizontal,),
],
),
);
}
}
SliverGridDelegateWithMaxCrossAxisExtent类用来声明纵轴子元素的最大长度,也是用于子元素的布局,确定子元素大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class _GridViewSampleAppPageState extends State<GridViewSampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GridView'),
),
body: GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150, // 最大长度
mainAxisSpacing: 10.0, // 主轴间距
crossAxisSpacing: 10.0, // 纵轴子元素间距
childAspectRatio: 1.0, // 纵轴与主轴长度比
),
children: <Widget>[
Icon(Icons.add),
Icon(Icons.update),
Icon(Icons.edit),
Icon(Icons.favorite),
Icon(Icons.face),
FlutterLogo(style: FlutterLogoStyle.horizontal,),
],
),
);
}
}
还可以使用GridView.count和GridView.extent快速创建GridView。GridView.count构造函数内部已经使用了SliverGridDelegateWithFixedCrossAxisCount,GridView.extent构造函数内部已经使用了SliverGridDelegateWithMaxCrossAxisExtent,所以直接使用即可。
GridView.count
1
2
3
4
5
body: GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
children: widgets,
),
GridView.extent
1
2
3
4
5
body: GridView.extent(
maxCrossAxisExtent: 150,
childAspectRatio: 1.0,
children: widgets,
),
类似于ListView,GridView也有GridView.builder方法,用于数据量较多的情况。GridView.builder有两个必须指定的参数:
- gridDelegate,用来确定怎么布局
- itemBuilder,用来确定每一个元素应该展示什么样的子widget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
List widgets = [];
@override
void initState() {
// TODO: implement initState
super.initState();
_getWidgets();
}
_getWidgets() {
for (var i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
getItem(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('row at inde: $i'),
),
onTap: () {
setState(() {
widgets.add(
getRow(widgets.length + 1)
);
print('tap row at $i');
});
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GridView'),
),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 三列
childAspectRatio: 1.0 //宽高相等
),
itemCount: widgets.length,
itemBuilder: (context, index) {
return getItem(index);
},
),
);
}
ScrollView
另外使用ListView还可以用来代替iOS中的UIScrollView,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class _ScrollViewSampleAppPageState extends State<ScrollViewSampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ScrollView'),
),
body: ListView(
padding: EdgeInsets.only(left: 20, top: 20),
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
Text('Row Five')
],
),
);
}
}
使用CustomScrollView
CustomScrollView的子widget必须使用sliver来自定义滚动模型(效果)的widget。它可以包含多种滚动模型。借助它可以实现多种混合风格的ListView,例如常见的卡片风格与列表风格混合在一起的效果。在Flutter中,Sliver通常指具有特定滚动效果的可滚动块。ListView和GridView都有对应的Sliver实现,例如SliverList,SliverGrid。Sliver本身不包含滚动交互模型(Scrollable Widget),所以在CustomScrollView中使用多个Sliver,CustomScrollView最终只会用一种统一的滑动效果,不会有iOS中常见的手势冲突。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class _CustomScrollViewSampleAppPageState extends State<CustomScrollViewSampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom ScrollView'),
),
body: CustomScrollView(
// 相当于iOS中的bunces属性,当设置为true时,未超过屏幕高度,不能滚动
shrinkWrap: false,
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.all(10),
sliver: SliverList(
delegate: SliverChildListDelegate(<Widget>[
Text('first line'),
Text('Second line'),
Text('Third line'),
Text('Fourth line'),
Text('Fifth line'),
Text('Sixth line'),
]),
),
)
],
),
);
}
}