跟 Android 中的 ListView 差不多,就是一个可滚动的列表,这种组件在开发中是很常用的。
ListView({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, List
根据显式的 Widget 集合来创建一个 ListView。
ListView.builder({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, @required IndexedWidgetBuilder itemBuilder, int itemCount, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, int semanticChildCount })
根据需要来自定义创建 ListView,该构造方法必传的一个参数为 itemBuilder,它是一个 IndexedWidgetBuilder ,它的构造方法中会返回 index,可以根据这个 index 来让 ListView 的子 Item 有不同的展示。
ListView.custom({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, @required SliverChildDelegate childrenDelegate, double cacheExtent, int semanticChildCount })
创建一个自定义子模型的 ListView。
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,这个分隔可以帮助我们实现分隔线的效果,它除了要传入一个 itemBuilder 之外,还要传入一个 separatorBuilder,这个就是分隔线。
childrenDelegate:自定义子模型时用到。
itemExtent:Item 的范围,scrollDirection 为 Axis.vertical 时限制高度,scrollDirection 为 Axis.horizontal 限制宽度。
cacheExtent:预加载的区域。
controller:滑动监听,值为一个 ScrollController 对象,这个属性应该可以用来做下拉刷新和上垃加载,后面详细研究。
padding:整个 ListView 的内间距。
physics:设置 ListView 如何响应用户的滑动行为,值为一个 ScrollPhysics 对象,它的实现类常用的有:
AlwaysScrollableScrollPhysics:总是可以滑动。
NeverScrollableScrollPhysics:禁止滚动。
BouncingScrollPhysics:内容超过一屏,上拉有回弹效果。
ClampingScrollPhysics:包裹内容,不会有回弹,感觉跟 AlwaysScrollableScrollPhysics 差不多。
primary:是否是与 PrimaryScrollController 关联的主滚动视图,若为 true 则 controller 必须为空。
reverse:Item 的顺序是否反转,若为 true 则反转。
scrollDirection:ListView 的方向,为 Axis.vertical 表示纵向,为 Axis.horizontal 表示横向。
shrinkWrap:不太明白。
itemCount:子 Item 数量,只有使用 new ListView.builder() 和 new ListView.separated() 构造方法的时候才能指定,其中 new ListView.separated() 是必须指定。
下面是一个设置了上述属性的 Demo:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//是否显示 debug 标签
debugShowCheckedModeBanner: false,
title: "ListView",
home: Scaffold(
appBar: new AppBar(
title: new Text("ListView"),
),
body: new Container(
child: new MyListView1(),
),
),
);
}
}
class MyListView1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
List widgetList = new List();
for (int i = 0; i < 100; i++) {
widgetList.add(new MyText("Item " + i.toString()));
}
ScrollController scrollController = new ScrollController();
scrollController.addListener(() {
print("scrollController--->" + scrollController.offset.toString());
});
return new ListView(
//Item 的范围,scrollDirection 为 Axis.vertical 时限制高度,scrollDirection 为 Axis.horizontal 限制宽度
itemExtent: 30,
// shrinkWrap: true,
//预加载的区域
// cacheExtent: 0,
//滑动监听,值为一个 ScrollController 对象
controller: scrollController,
// //内边距
padding: EdgeInsets.all(10),
//设置 ListView 如何响应用户的滑动行为,值为一个 ScrollPhysics 对象,它的实现类常用的有:
//AlwaysScrollableScrollPhysics:总是可以滑动
//NeverScrollableScrollPhysics:禁止滚动
//BouncingScrollPhysics:内容超过一屏,上拉有回弹效果
//ClampingScrollPhysics:包裹内容,不会有回弹,感觉跟 AlwaysScrollableScrollPhysics 差不多
physics: new BouncingScrollPhysics(),
//是否是与 PrimaryScrollController 关联的主滚动视图,若为 true 则 controller 必须为空
// primary: true,
//Item 的顺序是否反转,若为 true 则反转
reverse: true,
//ListView 的方向,为 Axis.vertical 表示纵向,为 Axis.horizontal 表示横向
// scrollDirection: Axis.vertical,
children: widgetList,
);
}
}
运行效果如下:
上面是用第一种构造方法创建的 ListView,接下来使用 Builder 来创建一下 ListView,它与普通的构造方法不同的是,普通构造方法传入的是已经创建好的子组件集合,而 Builder 是先指定子组件的个数,然后在 itemBuilder 中可以知道子组件所属的 position,根据这个 position 可以创建不同的子组件:
class MyListView2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
if (index.isOdd) {
return new Container(
padding: new EdgeInsets.all(15.0),
child: new Text(
"builder 奇数 Item " + index.toString(),
style:
new TextStyle(fontSize: 20.0, color: new Color(0xFFFF0000)),
),
);
} else {
return new Container(
padding: new EdgeInsets.all(15.0),
child: new Text(
"builder 偶数 Item " + index.toString(),
style:
new TextStyle(fontSize: 20.0, color: new Color(0xFF0000FF)),
),
);
}
},
);
}
}
运行效果如下:
其实使用 Builder 来构造 ListView 的话,也可以实现分隔线的效果,只需要根据 position 一个返回子组件,下一个返回分隔线组件即可:
class MyListView2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
if (index.isOdd) {
return Container(
padding: EdgeInsets.all(15.0),
child: Text(
"builder 奇数 Item " + index.toString(),
style:
new TextStyle(fontSize: 20.0, color: new Color(0xFFFF0000)),
),
);
} else {
return Divider(color: Color(0xFF000000),);
}
},
);
}
}
这样写出来会是这种效果:
虽然实现了分隔线效果,但是想必从 Item 上的文字就能看出问题,分隔线是占了 Item 的位置的,所以如果要用这种方式来实现分隔线的话,子组件的长度会增大一倍,因为一半都给了分隔线,所以实现分隔线的话一般是使用 ListView.separated() 构造方法:
class MyListView3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
List widgetList = new List();
for (int i = 0; i < 100; i++) {
widgetList.add(new MyText("Item " + i.toString()));
}
return new ListView.separated(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
print("itemBuilder--->" + index.toString());
return new Container(
padding: new EdgeInsets.all(15.0),
child: new Text(
"separated Item " + index.toString(),
style: new TextStyle(fontSize: 20.0, color: new Color(0xFF000000)),
),
);
},
separatorBuilder: (BuildContext context, int index) {
print("separatorBuilder--->" + index.toString());
return new Divider(
color: new Color(0xFF888888),
);
},
);
}
}
运行效果如下:
而且从打印中也可以看到分隔线并没有占据子组件空间,它的 position 跟子组件的 position 是一样的:
还有一种构造方法 ListView.custom() 这个暂时还没有去研究。水平方向的 ListView 很简单,只需要将 scrollDirection 设置为 Axis.horizontal 就行,简单贴个效果:
ListView 在开发中用得非常多,通常的场景是通过网络请求到一堆数据,然后通过 ListView 分页加载,这种下拉刷新和上拉加载等今后学到网络请求时再研究,应该有前辈都造好轮子了,到时候看看自己能不能实现。