Flutter中ListView中的使用
列表的使用场景
列表是在软件开发中经常使用的一种视图控件,便于显示结构化的数据,列表显示的是排序的数据,可以根据不同的排序规则进行显示(比如说订单列表可以根据时间正序排列,也可以按时间倒序排列,同样可以按照金额大小进行排序)。数据量小的时候,可能不用占满屏幕。如果数据量巨大的时候,可以占满屏幕,同时支持滚动条显示数据。列表的应用广泛,比如说显示朋友列表,商品列表,聊天记录列表,订单列表等等。Flutter中的ListView Widget可以帮助你实现一个列表。
列表的两种排列布局
ListView显示排列布局可以分为横向布局和纵向布局,也就是滚动方向是按水平滚动还是垂直滚动。ListView中还有一个属性是否逆向排序,示例代码如下所示:
body: ListView(
scrollDirection: Axis.horizontal,
reverse: false,
children: <Widget>[
for (int i = 0; i < 10; i++)
SizedBox(
width: 100,
child: ListTile(
title: Text('item $i'),
),
)
],
),
- scrollDirection代表滚动方向,Axis.horizontal代表水平方向,Axis.vertical代表垂直方向(默认)。
- reverse代表是否逆向排序,false代表正向排序(默认),true代表逆向排序。
不同类型的元素列表
我们经常需要创建展示不同类型内容的列表。比方说,我们可能在开发一个列表,它显示一个标题,后跟一些与标题相关的项目,然后是另一个标题,依此类推。比方说,我们可能开发一个和朋友的单独聊天页面,朋友的聊天内容靠左显示,显示用户名称和用户头像;自己的聊天内容靠右显示,仅仅显示用户头像。 效果图如下所示:
完整代码如下:
import 'package:flutter/material.dart';
class Message {
final bool isMine;
final String name;
final String avaUrl;
final String message;
Message(this.isMine, this.name, this.avaUrl, this.message);
}
abstract class MessageItem {
Widget build(BuildContext context);
}
class MyMessage extends MessageItem {
final Message message;
MyMessage(this.message);
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(message.message),
const SizedBox(
width: 10,
),
Image.asset(
message.avaUrl,
width: 40,
height: 40,
),
const SizedBox(
width: 10,
),
],
);
;
}
}
class FriendMessage extends MessageItem {
final Message message;
FriendMessage(this.message);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Image.asset(
message.avaUrl,
width: 40,
height: 40,
),
const SizedBox(
width: 10,
),
Column(
children: <Widget>[
Text(message.name),
Text(message.message),
],
),
],
);
}
}
void main() {
var messages = <Message>[];
messages.add(Message(false, "老李", "images/friend.png", "干什么呢"));
messages.add(Message(true, "技术蔡蔡", "images/my.jpg", "吃饭呢"));
messages.add(Message(false, "老李", "images/friend.png", "下午去干什么?"));
messages.add(Message(true, "技术蔡蔡", "images/my.jpg", "下午去打球"));
runApp(
MyApp(
items: messages,
),
);
}
class MyApp extends StatelessWidget {
final List<Message> items;
const MyApp({super.key, required this.items});
@override
Widget build(BuildContext context) {
final title = items.firstWhere((element) => !element.isMine).name;
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return item.isMine
? MyMessage(item).build(context)
: FriendMessage(item).build(context);
},
),
),
);
}
}
- 首先通过拉取和某个人(老李)的聊天记录(这里mock的数据)。
- 根据相同的数据源(Message)是否是自己的来创建不同的MessageItem,MessageItem子类分为FriendMessage和MyMessage,他们两者直接会创建不同的Widget,显示不同的页面元素。
- ListView.builder中具体显示构建不同的Widget的业务逻辑。
这个聊天记录页面还有需要优化的部分,比如聊天时间片段有间隔的时候需要显示时间,还有可以发送聊天的输入框,以及发送消息过程中的动画等效果。这里仅仅为了展示不同类型的列表,所以就没有完善。
ListView的两种构建方式的对比
标准的ListView构造函数适用于短列表,对于具有大量列表项的长列表,需要用ListView.builder构造函数来创建。
与标准的ListView构造函数需要一次性创建所有列表项不同的是,ListView.builder构造函数只在列表项从屏幕外滑入屏幕时才去创建列表项。
两种构建方式的不同导致,标准的ListView构造函数内部有大量数据的时候,会导致内存使用超过内存分配的问题,从而导致应用崩溃。
代码如下:
body: ListView(
// This next line does the trick.
scrollDirection: Axis.vertical,
reverse: false,
children: <Widget>[
for (int i = 0; i < 10000000000; i++)
SizedBox(
width: 100,
child: ListTile(
title: Text('item $i'),
),
)
],
),
当遇到加载大量数据的时候,可以考虑下面这几种方式:
- 我们可以用ListView.builder构建。
- 我们可以用分页的方式来分批次请求数据。
ListView当需要用分隔符的时候可以使用ListView.separated构建,separatorBuilder这个属性就是设置分隔符的。
ListView还有很多其他的属性,比如prototypeItem,physics,addAutomaticKeepAlives,cacheExtent等等,我就不一一介绍了,大家可以多查查文档看看源码,多多练习就可以了。
总结
大量结构化的数据如何安全高效地显示,一直是一个很有意思的课题。ListView是Flutter中经常使用的一个Widget,希望文章对您有帮助,如果文中有问题,希望您不吝指教。
转载自:https://juejin.cn/post/7390957334418751540