什么是基于Sliver的延迟构建模型呢?
通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;
如果一次性将子组件全部构建出将会非常浪费资源!
为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个“薄片”(Sliver),只有当Sliver出现在视图中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。
构造函数如下:
ListView({
...
//可滚动组件的公共参数
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
EdgeInsetsGeometry padding,
//ListView各个构造函数的共同参数
this.itemExtent,
bool shrinkWrap = false,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
//子组件列表
List children = const [],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
属性
scrollDirection
scrollDirection:决定子组件的滚动方向,默认是垂直方向
scrollDirection:Axis.horizontal,水平方向
scrollDirection:Axis.vertical,垂直方向
reverse
reverse:决定滚动方向是否与阅读方向一致
primary
primary:当内容不足以滚动时,是否支持滚动;值为true或者false
controller
此属性接收一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件。
physics
此属性接受一个ScrollPhysics类型的对象,它决定可滚动组件如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。
shrinkWrap
是否根据子组件的总长度来设置ListView的长度,默认值为false。
默认情况下,ListView会在滚动方向尽可能多的占用空间。
当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true,否则会报错。
children
children参数,是一个列表,但这种方式只适合少量子组件的情况。
因为这种方式需要将所有children都提前创建好(这需要做大量工作),而不是等到子组件真正显示的时候再创建,
也就是说通过默认构造函数构建的ListView没有应用基于Sliver的懒加载模型。
可滚动组件通过一个List来作为其children属性时,只适用于子组件较少的情况,这是一个通用规律
ListView.builder
上面的children只适合数据较少的情况下使用
而ListView.builder则适合列表项比较多(或者无限)的情况下使用,
因为只有当子组件真正显示的时候才会被创建,
通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的。
ListView.builder({
// ListView公共参数已省略
...
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
...
})
itemBuilder
它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget(就是一个组件)。
当列表滚动到具体的index位置时,会调用该构建器构建列表项,也就是所谓的基于Sliver的懒加载模型。
itemCount
该属性表示列表项的数量,如果为null,则表示无限列表
itemExtent
children的“长度”
如果滚动方向是垂直方向,则itemExtent代表子组件的高度;
如果滚动方向为水平方向,则itemExtent就代表子组件的宽度。
demo
ListView.builder(
itemCount: 100,
itemExtent: 50.0, //强制高度为50.0,如果这个值越来越小的话,那么显示的值是会重叠的
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
})
ListView.separated
ListView.separated在生成的列表项之间添加一个分割组件。
比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器。
在奇数行添加一条蓝色下划线,偶数行添加一条红色下划线
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State {
@override
Widget build(BuildContext context) {
//下划线widget预定义以供复用。
Widget Lineblue = Divider(color: Colors.blue);
Widget Linered = Divider(color: Colors.red);
return Scaffold(
appBar: AppBar(
title: Text(
"ListView.separated",
// style: TextStyle(color: Color(0xFF1E88E5)),
),
),
body: ListView.separated(
itemCount: 100,
//列表项构造器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
//分割器构造器
separatorBuilder: (BuildContext context, int index) {
return index % 2 == 0 ? Lineblue : Linered;
},
));
}
}
无限加载列表
从数据源异步分批拉取数据,然后用ListView展示。
滑动到列表末尾时,判断是否需要再去拉取数据,
如果是,则去拉取,拉取过程中在表尾显示一个转着的小圆圈,拉取成功后将数据插入列表;
如果不需要再去拉取,则在表尾提示"没有更多了"
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State {
static const loadingTag = "##loading##"; //表尾标记
var _words = [loadingTag];
@override
void initState() {
super.initState();
_retrieveData();
}
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: _words.length,
itemBuilder: (context, index) {
//如果到了表尾
if (_words[index] == loadingTag) {
//不足100条,继续获取数据
if (_words.length - 1 < 100) {
//获取数据
_retrieveData();
//加载时显示loading
return Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(strokeWidth: 2.0)),
);
} else {
//已经加载了100条数据,不再获取数据。
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text(
"没有更多了",
style: TextStyle(color: Colors.grey),
));
}
}
//显示单词列表项
return ListTile(title: Text(_words[index]));
},
separatorBuilder: (context, index) => Divider(height: .0),
);
}
void _retrieveData() {
Future.delayed(Duration(seconds: 2)).then((e) {
_words.insertAll(
_words.length - 1,
//每次生成20个单词
generateWordPairs().take(20).map((e) => e.asPascalCase).toList());
setState(() {
//重新构建列表
});
});
}
}
ListTile
ListTile通常用于在 Flutter 中填充 ListView
const ListTile({
Key key,
this.leading,
this.title,
this.subtitle,
this.trailing,
this.isThreeLine = false,
this.dense,
this.contentPadding,
this.enabled = true,
this.onTap,
this.onLongPress,
this.selected = false,
})
title参数可以接受任何小组件,但通常是文本小组件
ListTile(
title: Text('我喜欢你!'),
)
subtitle
副标题,显示在标题(title)下面较小的文本
ListTile(
title: Text('我喜欢你!'),
subtitle: Text('你喜欢我吗?'),
)
dense
使文本更小,并将所有内容打包在一起
ListTile(
title: Text('我喜欢你!'),
subtitle: Text('你喜欢我吗?'),
dense:true,
)
leading
将图像或图标添加到列表的开头
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense:true,
)
trailing
在列表的末尾放置一个图像
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense:true,
trailing: Icon(Icons.keyboard_arrow_right),
)
contentPadding
设置内容边距,默认是 16
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense:true,
trailing: Icon(Icons.keyboard_arrow_right),
contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
)
selected
如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense:true,
trailing: Icon(Icons.keyboard_arrow_right),
contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
selected: true,
)
onTap、onLongPress
onTap 为单击,onLongPress 为长按
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense:true,
trailing: Icon(Icons.keyboard_arrow_right),
contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
selected: true,
onTap: () {
// do something
},
onLongPress: (){
// do something else
},
)
enabled
通过将 enable 设置为 false,来禁止点击事件
** ListTile的demo**
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ListTile'),),
body: Column(
children: [
ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage('images/Test.jpg'),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense: true,
trailing: Icon(Icons.keyboard_arrow_right),
contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
// selected: true,
onTap: () {
// do something
},
onLongPress: () {
// do something else
},
),
ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage('images/Test.jpg'),
),
title: Text('我喜欢你'),
subtitle: Text('你喜欢我吗?'),
dense: true,
trailing: Icon(Icons.keyboard_arrow_right),
contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
selected: true,
onTap: () {
// do something
},
onLongPress: () {
// do something else
},
)
],
));
}
}
添加固定列表头
很多时候需要给列表添加一个固定表头
需要让ListView自动拉伸以适应屏幕,这个时候就需要我们使用到弹性布局Flex
可以使用Expanded自动拉伸组件大小,Column是继承自Flex的,所以可以直接使用Column+Expanded来实现
Column(children: [
ListTile(title: Text("数字列表")),
Expanded(
child: ListView.builder(itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}),
),
]);
4种构建方式
- listview - 这种构建方式是把固定数据变成 item 传给 childen
Widget build(BuildContext context) {
// TODO: implement build
return ListView(
padding: EdgeInsets.all(20),
children: [
Text("AA"),
Text("BB"),
Text("CC"),
],
);
}
- ListView.builder - 构建一种 item 类型的列表,当然在 itemBuilder 使用 if、slse 也是能支持多类型 item 的,itemCount 是列表数量
Widget build(BuildContext context) {
// TODO: implement build
return ListView.builder(
padding: EdgeInsets.all(20),
itemCount: 50,
itemBuilder: (context, index) {
return Text("item:${index}");
},
);
}
- ListView.separated - 带分割线的列表, Flutter中把分割线也是看成一个 widget 来处理的,这里新增了 separatorBuilder 来返回分割线 widget,不过分割线的特点是:不包含最后一项,也就是分割线不会出列表的范围。这中设计我很喜欢,把分割线或是分隔符看成列表的 item,可以极大的方便我们设计列表,可玩性灵活性可以大大增加,至少我们可以根据 index 找到前后 item 的类型,然后考虑可以采取不同类型的分隔 item,最典型的应用就是插入广告了,不用对列表有任何修改
Widget build(BuildContext context) {
// TODO: implement build
return ListView.separated(
padding: EdgeInsets.all(20),
itemCount: 50,
itemBuilder: (context, index) {
return Text("item:${index}");
},
separatorBuilder: (context,index){
return Container(
height: 1,
color: Colors.pink,
);
},
);
}
- ListView.custom() - 需要传入一个实现了 SliverChildDelegate 的组件,如 SliverChildListDelegate 和 SliverChildBuilderDelegate。这里我不详细说,因为没必要,SliverChildListDelegate 就是 listview,SliverChildBuilderDelegate 就是 ListView.builder。这个 ListView.custom 用的也比较少
参考资料
Flutter 滚动控件篇-->ListView
Flutter - Listview 详解
flutter - listView 下拉刷新 上拉加载
Flutter 滚动控件篇-->滚动监听及控制(ScrollController)