文章已同步至【个人博客】,欢迎访问【我的主页】😃
文章地址:blog.fanjunyang.zone/archives/fl…
Flutter 中的ListView
是最常用的可滚动的组件之一,
它可以沿一个方向线性排布所有子组件,并且它也支持基于Sliver的延迟构建模型。
基于Sliver的延迟构建
什么是基于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<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
属性解释
scrollDirection
决定子组件的滚动方向(排列方向),默认是垂直方向
scrollDirection:Axis.horizontal,
水平方向
scrollDirection:Axis.vertical,
垂直方向
reverse
决定滚动方向是否与阅读方向一致
primary
当内容不足以滚动时,是否支持滚动;
值为true
或者false
,我试了一下,好像没什么卵用,不知道是理解错了,还是怎么的,先写上吧
controller
此属性接收一个ScrollController
对象。ScrollController
的主要作用是控制滚动位置和监听滚动事件。
有关ScrollController
的使用及详情,请参考Flutter 滚动控件篇–>滚动监听及控制(ScrollController)
默认情况下,Widget树中会有一个默认的PrimaryScrollController
,如果子树中的可滚动组件没有显式的指定controller
,并且primary
属性值为true
时(默认就为true
),可滚动组件会使用这个默认的PrimaryScrollController
。这种机制带来的好处是父组件可以控制子树中可滚动组件的滚动行为,
physics
此属性接受一个ScrollPhysics
类型的对象,它决定可滚动组件如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。
在iOS上会出现弹性效果,而在Android上会出现微光效果。
shrinkWrap
该属性表示是否根据子组件的总长度来设置ListView
的长度,默认值为false
。
默认情况下,ListView
会在滚动方向尽可能多的占用空间。当ListView
在一个无边界(滚动方向上)的容器中时,shrinkWrap
必须为true
,否则会报错。
itemExtent
该参数如果不为null
,则会强制children
的“长度”为itemExtent
的值;
这里的“长度”是指滚动方向上子组件的长度,也就是说如果滚动方向是垂直方向,则itemExtent
代表子组件的高度;如果滚动方向为水平方向,则itemExtent
就代表子组件的宽度。
addAutomaticKeepAlives
该属性表示是否将列表项(子组件)包裹在AutomaticKeepAlive
组件中;
在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive
中,在该列表项滑出视口时也不会被回收,它会使用KeepAliveNotification
来保存其状态。如果列表项自己维护其KeepAlive
状态,那么此参数必须置为false
。
addRepaintBoundaries
该属性表示是否将列表项(子组件)包裹在RepaintBoundary
组件中。
当可滚动组件滚动时,将列表项包裹在RepaintBoundary
中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary
反而会更高效。和addAutomaticKeepAlive
一样,如果列表项自己维护其KeepAlive
状态,那么此参数必须置为false
。
addSemanticIndexes
该属性表示是否把子控件包装在IndexedSemantics
里,用来提供无障碍语义
cacheExtent
可见区域的前后会有一定高度的空间去缓存子控件,当滑动时就可以迅速呈现
简单的就是说,当你快要滑到加载数据的时候,他已经提前一步加载好了,等到你滑到的时候就会显示出来,而不至于用户滑到的时候还需要等待一会儿。
semanticChildCount
有含义的子控件的数量
如:ListView
会用children
的长度,而ListView.separated
会用children
长度的一半
children
这里的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
,则表示无限列表
注:可滚动组件的构造函数如果需要一个列表项Builder,那么通过该构造函数构建的可滚动组件通常就是支持基于Sliver的懒加载模型的,反之则不支持,这是个一般规律。
代码示例:
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<CategoryPage> {
@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
展示。
当我们滑动到列表末尾时,判断是否需要再去拉取数据,如果是,则去拉取,拉取过程中在表尾显示一个转着的小圆圈,拉取成功后将数据插入列表;如果不需要再去拉取,则在表尾提示”没有更多了”。
这里我们需要安装一个包english_words: ^3.1.5
(在pubspec.yaml
文件中的dependencies
下安装),可以给我们自动的生成英语单词
代码示例:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
static const loadingTag = "##loading##"; //表尾标记
var _words = <String>[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(() {
//重新构建列表
});
});
}
}
_retrieveData()
的功能是模拟从数据源异步获取数据,
english_words
包的generateWordPairs()
方法可以每次生成20个单词。
运行效果:
ListTile
这里我们说一下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
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
我这里设置30
代码示例:
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
,来禁止点击事件
这里就不写代码了,比较简单。
属性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<CategoryPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ListTile'),),
body: Column(
children: <Widget>[
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
,如果不知道的话,请移步Flutter 布局控件篇–>Flex、Expanded
我们可以使用Expanded
自动拉伸组件大小,并且我们也说过Column
是继承自Flex
的,所以我们可以直接使用Column+Expanded
来实现,
代码示例:
Column(children: <Widget>[
ListTile(title: Text("数字列表")),
Expanded(
child: ListView.builder(itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}),
),
]);
运行效果:
T_T
今天的文章Flutter 滚动控件篇–>ListView分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18638.html