下面我们来介绍下Flutter中的滑动控件SingleChildScrollView、列表ListView和表格GridView

SingleChildScrollView

SingleChildScrollView类似于Android中的ScrollView,我使用的较浅,在我目前看来,他和ScrollView的唯一区别就是它还可以横向滚动。

我们先来看下他的定义:

1
2
3
4
5
6
7
8
9
SingleChildScrollView({
this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
this.reverse = false,
this.padding,
bool primary,
this.physics,
this.controller,
this.child,
})

  • scrollDirection:设定滚动的方向,可以设定Axis.vertical或者Axis.horizon
  • reverse:是否按照阅读方向的反方向滑动,emmm这个可能有点觉得莫名其妙,但是如阿拉伯语言地区,他们阅读和我国古时候一样是从右向左的,所以如果reversetrue,且滚动方向是水平滚动的话,如果系统时阿拉伯语等从右向左阅读的语言时,方向是从右向左的;
  • padding:留白的大小,和Padding的用法一样,通过EdgeInsets来设定,具体可以看前几章将Flutter基础Widget;
  • primary:指是否使用widget树中默认的PrimaryScrollController;当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且没有指定controller时,primary默认为true
  • physics:用于控制滚动方式,这个等会重点讲解;
  • controller:用于滚动监听及控制;
  • child:子Widget。

physics

这个参数是用于控制滚动方式,有几种参数:

  • NeverScrollablePhysics:呈现不可滚动状态;
  • BouncingScrollPhysics:当列表滑动结束时,会回弹列表,类似于iOS的列表滑动效果;
    BouncingScrollPhysics

  • ClampingScrollPhysics:滑动结束时会显示水波纹阴影,类似于Android的列表滑动效果;
    ClampingScrollPhysics

  • FixedExtentScrollPhysics:可以自己制作滑动效果,但是我也不会,所以在此不做解释😝。

ListView

ListView可以沿一个方向上线性排布所有子组件(不仅局限于竖直方向)。

让我们来看下他的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ListView({
...
//可滚动widget公共参数
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
EdgeInsetsGeometry padding,

//ListView各个构造函数的共同参数
double itemExtent,
bool shrinkWrap = false,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,

//子widget列表
List<Widget> children = const <Widget>[],
})

公共属性我们就不讲了,上面SingleChildScrollView已经讲过了,我们现在只讲他特有的:

  • itemExtent:用于控制ListView的长度,如果不为null,则强制所有子Widget合起来的长度小于设定的值:如果ListView是横向的,则所有子Widget横向长度的和小于它;如果ListView是纵向的,则所有子Widget纵向长度的和小于它;
  • shrinkWrap:该属性表示是否根据子组件的总长度来设置ListView的长度,默认值为false 。默认情况下,ListView的会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true
  • addAutomaticKeepAlives:该属性表示是否将列表项(子组件)包裹在AutomaticKeepAlive组件中;典型地,在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive中,在该列表项滑出视口时它也不会被GC(垃圾回收),它会使用KeepAliveNotification来保存其状态。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
  • addRepaintBoundaries:该属性表示是否将列表项(子组件)包裹在RepaintBoundary组件中。当可滚动组件滚动时,将列表项包裹在RepaintBoundary中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效。和addAutomaticKeepAlive一样,如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
  • cacheExtent:设定缓存大小。

默认情况下一个一个设定children很麻烦,所以为了方便,Flutter还提供了一个builder构造方法:

ListView.builder

1
2
3
4
5
6
7
ListView.builder({
// ListView公共参数已省略
...
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
...
})
  • itemCount:是需要加载子Widget的长度;
  • itemBuilder:列表项的构造器,当列表滚动到具体的index位置时,会调用该构建器构建列表项。

看例子:

1
2
3
4
5
6
7
8
9
10
11
12
ListView.builder(
itemCount: _provinces.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ListTile(
title: Text("$index"),
),
onTap: () {}));
},
);
},
)

ListViewBUilde

ListView.separated

用于添加分割线。相较于ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器。

下面我们看一个例子:奇数行添加一条蓝色下划线,偶数行添加一条绿色下划线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ListView3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
//下划线widget预定义以供复用。
Widget divider1=Divider(color: Colors.blue,);
Widget divider2=Divider(color: Colors.green);
return ListView.separated(
itemCount: 100,
//列表项构造器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
//分割器构造器
separatorBuilder: (BuildContext context, int index) {
return index%2==0?divider1:divider2;
},
);
}
}

-w160

GridView

GridView可以构造一个网格列表,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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>[],
})

大部分参数和ListView都相同,我们只关注gridDelegate这个参数:
gridDelegate作用是控制GridView子组件如何排列,Flutter中提供了两个类SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent,我们可以直接使用,下面我们分别来介绍一下它们。

SliverGridDelegateWithFixedCrossAxisCount

该子类实现了一个横轴为固定数量子元素的layout算法,其构造函数为:

1
2
3
4
5
6
SliverGridDelegateWithFixedCrossAxisCount({
@required double crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})

  • crossAxisCount:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商。
  • mainAxisSpacing:主轴方向的间距。
  • crossAxisSpacing:横轴方向子元素的间距。
  • childAspectRatio:子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度。

SliverGridDelegateWithMaxCrossAxisExtent

该子类实现了一个横轴子元素为固定最大长度的layout算法,其构造函数为:

1
2
3
4
5
6
SliverGridDelegateWithMaxCrossAxisExtent({
double maxCrossAxisExtent,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})

maxCrossAxisExtent为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的。其它参数和SliverGridDelegateWithFixedCrossAxisCount相同。

GridView.builder

ListView一样,当子Widget数量较多时,也提供了builder方法:

1
2
3
4
5
GridView.builder(
...
@required SliverGridDelegate gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
)

这个就不在这举例了,gridDelegate和前面一样的,itemBuilderListView的一样的。