Flutter可滚动组件(2):ListView基本使用
ListView 是一个滚动列表组件,可以在垂直方向上(或水平方向,默认是垂直方向)展示一系列的子组件。一种最简单的使用方式是直接将所有需要排列的子 Widget 放在 ListView 的 children 属性中即可。
一、ListView的构造方法
ListView 有下面三种构造方法:
- 常规方法,即直接使用 ListView 的构造函数
ListView.builder
方法ListView.separated
方法
1.1 常规方法
ListView 的常规方法就是直接使用 ListView 的构造函数来构造 ListView 中的各个 item。其中 ListView 有一个 children 属性,它接收一个 widget 的 list,这个 list 就是 ListView 中要呈现的对象。
我们来构造一个拥有 100 个 item 的 ListView 对象:
import 'package:flutter/material.dart';
main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: MyHomeBody(),
),
);
}
}
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
final TextStyle textStyle = const TextStyle(fontSize: 20, color: Colors.redAccent);
@override
Widget build(BuildContext context) {
return ListView(
children: List<Widget>.generate(100, (i) => Text('列表 $i')),
);
}
}
效果图如下所示:
1.2 ListView.builder方法
上面的例子中,我们简单的使用List.generate
方法生成了 100 个对象。通过构造函数中的 children 传入所有的 item 有一个问题:默认会创建出所有的子 item。
在 item 数目比较少的情况下是没有任何问题的,如果 item 数目比较多的情况下,就会产生性能问题,而且会增加首屏的渲染时间。幸好,ListView 还提供了一个ListView.builder
的方法,适用于 item 比较多的场景,该构造函数将创建 item 交给了一个抽象的方法,交给 ListView 进行管理,ListView 会在真正需要的时候去创建子 Widget,而不是一开始就全部初始化好。。
首先,我们构建一个 items list,并将其传入 MyApp 的 StatelessWidget 中:
MyApp(
items: List<String>.generate(10000, (i) => '列表 $i'),
)
然后就可以在 MyApp 的 body 中使用ListView.builder
来构建 item 了:
body: ListView.builder(
itemCount: items.length,
prototypeItem: ListTile(
title: Text(items.first),
),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)
ListView.builder
是推荐用来创建 ListView 的方式,上面的完整代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(
MyApp(
items: List<String>.generate(10000, (i) => '列表 $i'),
),
);
}
class MyApp extends StatelessWidget {
final List<String> items;
const MyApp({super.key, required this.items});
@override
Widget build(BuildContext context) {
const title = 'ListView的使用';
return MaterialApp(
debugShowCheckedModeBanner: false,
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: ListView.builder(
itemCount: items.length,
prototypeItem: ListTile(
title: Text(items.first),
),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
),
),
);
}
}
效果图如下所示:
1.3 ListView.separated方法
ListView.separated
是ListView.builder
的一个变种,它在每个项之间插入了一个分隔符。你可以使用separatorBuilder
属性来定义分隔符的样式和构建方法。下面是一个例子:
import 'package:flutter/material.dart';
void main() {
runApp(
MyApp(
items: List<String>.generate(10000, (i) => '列表 $i'),
),
);
}
class MyApp extends StatelessWidget {
final List<String> items;
const MyApp({super.key, required this.items});
@override
Widget build(BuildContext context) {
const title = 'ListView的使用';
return MaterialApp(
debugShowCheckedModeBanner: false,
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: ListView.separated(
itemCount: items.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
),
),
);
}
}
效果图如下所示:
二、自定义ListView的样式和布局
你可以使用ListView.builder
的itemBuilder
属性来自定义列表项的样式和布局。下面是一个例子,展示如何自定义列表项:
class MyApp extends StatelessWidget {
final List<String> items;
const MyApp({super.key, required this.items});
@override
Widget build(BuildContext context) {
const title = 'ListView的使用';
return MaterialApp(
debugShowCheckedModeBanner: false,
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Container(
color: index % 2 == 0 ? Colors.grey[200] : Colors.white,
padding: const EdgeInsets.all(16),
child: Text(items[index]),
);
},
),
),
);
}
}
效果图如下所示:
三、ListView的性能优化
当使用ListView显示大量数据时,为了提高性能,你可以考虑以下几种优化方式:
- 使用
ListView.builder
或ListView.separated
来按需构建列表项,避免一次性构建所有的项。 - 使用
ListView.separated
并提供合适的分隔符构建方法,避免不必要的分隔符构建。 - 如果列表项有固定高度,请使用
itemExtent
属性来指定项的高度,以避免动态计算高度带来的性能开销。 - 如果列表项是不可滚动的,可以将
physics
属性设置为NeverScrollableScrollPhysics
来禁用滚动。 - 使用
ScrollController
来控制滚动,并使用addPostFrameCallback
在构建完成后延迟加载数据。
四、ListView的常见问题和解决方法
在使用ListView时,你可能会遇到一些常见的问题。以下是其中一些问题和相应的解决方法:
- ListView无法滚动:确保包含ListView的容器(如Scaffold)具有足够的空间来进行滚动,或者尝试将ListView的父级包装在一个可滚动的容器(如SingleChildScrollView)中。
- ListView不显示或空白:检查是否正确设置了
itemCount
属性,并且itemBuilder
方法返回了有效的项。 - ListView滚动不流畅:可能是由于列表项过多或构建过程中的性能问题导致的。尝试使用按需构建的ListView.builder或ListView.separated,并使用性能优化技术来提高滚动性能。
五、示例
9.1 垂直列表
import 'package:flutter/material.dart';
main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: MyHomeBody(),
),
);
}
}
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: const [
ListTile(
leading: Icon(
Icons.assignment,
color: Colors.red,
// size: 40,
),
title: Text("全部订单"),
),
Divider(),
ListTile(
leading: Icon(
Icons.payment,
color: Colors.green,
// size: 40,
),
title: Text("待付款"),
),
Divider(),
ListTile(
leading: Icon(
Icons.local_car_wash,
color: Colors.orange,
// size: 40,
),
title: Text("待收货"),
),
Divider(),
ListTile(
leading: Icon(
Icons.favorite,
color: Colors.lightGreen,
// size: 40,
),
title: Text("我的收藏"),
),
Divider(),
ListTile(
leading: Icon(
Icons.people,
color: Colors.black54,
// size: 40,
),
title: Text("在线客服"),
),
Divider(),
],
);
}
}
效果图如下所示:
9.2 垂直图文列表
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: [
ListTile(
leading: Image.network("https://www.itying.com/images/flutter/1.png"),
title: const Text("华北黄淮高温雨今起强势登场"),
subtitle: const Text("中国天气网讯 21日开始,华北黄淮高温雨今起强势登场"),
),
const Divider(),
ListTile(
trailing: Image.network("https://www.itying.com/images/flutter/1.png"),
title: const Text("华北黄淮高温雨今起强势登场"),
subtitle: const Text("中国天气网讯 21日开始,华北黄淮高温雨今起强势登场"),
),
const Divider(),
ListTile(
leading: Image.network("https://www.itying.com/images/flutter/1.png"),
title: const Text("华北黄淮高温雨今起强势登场"),
// subtitle: const Text("中国天气网讯 21日开始,华北黄淮高温雨今起强势登场"),
),
const Divider(),
],
);
}
}
效果图如下所示:
9.3 水平列表
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 180,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
ClipOval(
//圆角
child: Container(
width: 180,
color: Colors.red,
),
),
Container(
width: 180,
color: Colors.green,
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Column(
children: [
Image.network(
"https://www.itying.com/images/flutter/1.png"),
const Text("第一个文本"),
],
),
)),
Container(
color: Colors.orange,
width: 180,
),
],
),
);
}
}
效果图如下所示:
9.4 动态列表
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
List<Widget> _getData() {
List<Widget> list = [];
for (var i = 0; i < 10; i++) {
list.add(const ListTile(
title: Text("列表"),
));
}
return list;
}
@override
Widget build(BuildContext context) {
return ListView(
children: _getData(),
);
}
}
效果图如下所示:
9.5 builder 实现动态列表
// ignore: must_be_immutable
class MyHomeBody extends StatelessWidget {
List<String> list = [];
MyHomeBody({super.key}) {
for (var i = 0; i < 10; i++) {
list.add("列表 -- $i");
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: list.length,//数量
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(list[index]),
);
},
);
}
}
效果图如下所示:
9.6 builder 实现动态列表 -- 获取 json 数据
// listData 中测试数据,使用时需要导入头文件
List listData=[
{
"title": 'Candy Shop',
"author": 'Mohamed Chahin',
"imageUrl": 'https://www.itying.com/images/flutter/1.png',
},
{
"title": 'Childhood in a picture',
"author": 'Google',
"imageUrl": 'https://www.itying.com/images/flutter/2.png',
},
{
"title": 'Alibaba Shop',
"author": 'Alibaba',
"imageUrl": 'https://www.itying.com/images/flutter/3.png',
},
{
"title": 'Candy Shop',
"author": 'Mohamed Chahin',
"imageUrl": 'https://www.itying.com/images/flutter/4.png',
},
{
"title": 'Tornado',
"author": 'Mohamed Chahin',
"imageUrl": 'https://www.itying.com/images/flutter/5.png',
},
{
"title": 'Undo',
"author": 'Mohamed Chahin',
"imageUrl": 'https://www.itying.com/images/flutter/6.png',
},
{
"title": 'white-dragon',
"author": 'Mohamed Chahin',
"imageUrl": 'https://www.itying.com/images/flutter/7.png',
}
];
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: listData.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: Image.network(listData[index]["imageUrl"]),
title: Text(listData[index]["title"]),
subtitle: Text(listData[index]["author"]),
);
},
);
}
}
效果图如下所示:
参考:
flutter系列之:flutter中listview的高级用法-腾讯云开发者社区-腾讯云
Flutter速来系列23-1、ListView和GridView,躲不掉避不开的列表一、引言 在Flutter中,Li - 掘金
Flutter 组件之 ListView、GridView - 简书
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型