Flutter 列表组件
列表布局是我们项目开发中最常用的一种布局方式。Flutter 中我们可以通过 ListView 来定义 列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有一下 分类:
1.垂直列表
2.垂直图文列表
3.水平列表
4.动态列表
5.矩阵式列表
构造方法
方法一
默认构造函数采用子类的显式。此构造函数适用于具有少量(有限个)子项的列表视图,因为构造List需要为可能在列表视图中显示的每个子项执行工作,而不仅仅是那些实际可见的子项
class ListView extends BoxScrollView {
/// Creates a scrollable, linear array of widgets from an explicit [List].
///
/// This constructor is appropriate for list views with a small number of
/// children because constructing the [List] requires doing work for every
/// child that could possibly be displayed in the list view instead of just
/// those children that are actually visible.
///
/// It is usually more efficient to create children on demand using [new
/// ListView.builder].
///
/// The `addAutomaticKeepAlives` argument corresponds to the
/// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
/// `addRepaintBoundaries` argument corresponds to the
/// [SliverChildListDelegate.addRepaintBoundaries] property. The
/// `addSemanticIndexes` argument corresponds to the
/// [SliverChildListDelegate.addSemanticIndexes] property. None
/// may be null.
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
}) : childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount ?? children.length,
dragStartBehavior: dragStartBehavior,
);
方法二
它构造函数采用IndexedWidgetBuilder它根据需要构建子项。此构造函数适用于具有大量(或无限)子项数的列表视图,因为仅为实际可见的子项调用构建器。
长列表时采用builder模式,能提高性能。不是把所有子控件都构造出来,而是在控件viewport加上头尾的cacheExtent这个范围内的子Item才会被构造。在构造时传递一个builder,按需加载是一个惯用模式,能提高加载性能。
ListView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
}) : childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount ?? itemCount,
dragStartBehavior: dragStartBehavior,
);
方式三
它的构造函数有两个IndexedWidgetBuilder 构建器: itemBuilder
根据需要构建子项,separatorBuilder
类似地构建出现在子项之间的分隔子项。此构造函数适用于具有固定数量子项的列表视图。
列表中需要分割线时,可以自定义复杂的分割线
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,
}) : assert(itemBuilder != null),
assert(separatorBuilder != null),
assert(itemCount != null && itemCount >= 0),
itemExtent = null,
childrenDelegate = SliverChildBuilderDelegate(
(BuildContext context, int index) {
final int itemIndex = index ~/ 2;
Widget widget;
if (index.isEven) {
widget = itemBuilder(context, itemIndex);
} else {
widget = separatorBuilder(context, itemIndex);
assert(() {
if (widget == null) {
throw FlutterError('separatorBuilder cannot return null.');
}
return true;
}());
}
return widget;
},
childCount: _computeSemanticChildCount(itemCount),
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
semanticIndexCallback: (Widget _, int index) {
return index.isEven ? index ~/ 2 : null;
},
),
super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: _computeSemanticChildCount(itemCount),
);
方式四
构造需要SliverChildDelegate提供自定义子项的其他方面的能力。例如,SliverChildDelegate可以控制用于估计实际上不可见的子项大小的算法。
上面几种模式基本可以满足业务需求,如果你还想做一些其它设置(如列表的最大滚动范围)或获取滑动时每次布局的子Item范围,可以尝试custom模式
const ListView.custom({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required this.childrenDelegate,
double cacheExtent,
int semanticChildCount,
}) : assert(childrenDelegate != null),
super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount,
);
常用属性
属性名 | 功能 | 值所属类型 |
---|---|---|
children | 列表元素 | List<Widget> |
scrollDirection | Axis.horizontal 水平列表Axis.vertical 垂直列表 | Axis |
padding | 内边距 | EdgeInsetsGeometry |
resolve | 组件反向排序 | bool |
基本使用
/*
*listView 的基本使用方法
*/
class MyListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
),
ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
),
ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
),
ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
),
ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
),ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
),
ListTile(
leading: Icon(Icons.phone),
title: Text(
"你好数据的交换机",
style: TextStyle(fontSize: 28.0),
),
subtitle: Text(
"listview listview",
style: TextStyle(fontSize: 18.0),
),
)
],
),
);
}
}
ListView.separated带有下划线的ListView
/**
* 带有下划线的listview
*/
class MyListView2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.separated(
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)),
),
);
}
},
separatorBuilder: (BuildContext context, int index) {
return new Divider(
color: new Color(0xFF888888),
);
},
);
}
}
ListView.builder 的用法
@override
Widget build(BuildContext context) {
return 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)),
),
);
}
},
);
}
}
ListView.custom 的用法
class MyListView1 extends StatefulWidget {
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView1> {
List<String> items = <String>['1', '2', '3', '4', '5'];
void _reverse() {
setState(() {
items = items.reversed.toList();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView.custom(
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return KeepAlive(
data: items[index],
key: ValueKey<String>(items[index]),
);
},
childCount: items.length,
findChildIndexCallback: (Key key) {
final ValueKey valueKey = key;
final String data = valueKey.value;
return items.indexOf(data);
}),
),
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () => _reverse(),
child: Text('Reverse items'),
),
],
),
),
);
}
}
网络请求
/*
* 请求网络数据
*/
class MyListView3 extends StatelessWidget {
List<Widget> _getListData() {
var tempList = listData.map((value) {
return ListTile(
title: Text(value["title"]),
leading: Image.network(value["imageUrl"],fit: BoxFit.cover,));
// return Image(
// image: NetworkImage(value["imageUrl"]),
// width: 300.0,
// height: 200.0,
// fit: BoxFit.cover,
// );
});
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return ListView(children: this._getListData());
}
}