Flutter数据驱动列表插件list_view_with_data_source的使用
Flutter数据驱动列表插件list_view_with_data_source
的使用
ListViewWithDataSource
是一个用于在 Flutter 中构建具有多个类型的列表项的 ListView
插件。它允许你通过定义不同的 Section 和 Item 来动态生成列表内容。
关于
该插件抽象了 ListView
中需要展示元素的构造过程,使得管理变得更加容易。当你需要展示多种类型的数据时,它非常有用。通过分组(Sections)的概念,你可以为每个组设置不同的分隔符,并自由地插入头部和尾部。
它受到了 UICollectionViewDataSource
的启发。
与 grouped_list
插件相比,它的主要区别在于:
- 不自动化组的构建,因此可以表达更复杂的切换。
- 可以单独设置头部/尾部/组分隔符的小部件。
- 通过分离显示结构和小部件的构建,结构本身可以被验证。
示例
这是一个根据每组数据条目数量来改变显示项目的例子。
完整的示例代码可以在 example/lib/main.dart
中查看。
步骤 1:添加依赖包
在 pubspec.yaml
文件中添加依赖:
dependencies:
list_view_with_data_source: ^1.0.0
步骤 2:定义 Section 和 Item
定义 ProjectSection
和 ProjectSectionItem
类,这些类将用于存储数据源中的数据。
class ProjectSection extends Equatable {
const ProjectSection({
required this.project,
required this.itemCount,
});
final Project project;
final int itemCount;
String get title => project.name;
String get trailing =>
'${project.tasks.where((task) => task.isComplete).length}/${project.tasks.length}';
[@override](/user/override)
List<Object?> get props => [project, itemCount];
}
sealed class ProjectSectionItem {
const ProjectSectionItem();
String get title;
}
class TaskItem extends ProjectSectionItem {
const TaskItem({
required this.task,
});
final Task task;
[@override](/user/override)
String get title => task.name;
String get subtitle => task.type;
}
class EmptyItem extends ProjectSectionItem {
const EmptyItem();
[@override](/user/override)
String get title => 'No tasks';
}
步骤 3:填充数据源
根据数据填充预定义的 Section 和 Item 到 ListViewDataSource
中。
final dataSource = CustomDataSource();
for (final project in projects) {
final desktopTasks = project.tasks.whereType<DesktopTask>().toList();
final section =
ProjectSection(project: project, itemCount: desktopTasks.length);
dataSource.addSection(section);
if (desktopTasks.isNotEmpty) {
dataSource.appendItems(
section, desktopTasks.map((e) => TaskItem(task: e)).toList());
} else {
dataSource.appendItem(section, const EmptyItem());
}
}
步骤 4:构建视图
在屏幕上放置 ListViewWithDataSource
小部件并开始构建视图。
ListViewWithDataSource(
dataSource: _model.dataSourceFor(_filter),
itemBuilder:
(context, section, item, sectionIndex, itemIndex) =>
switch (item) {
(final TaskItem item) => ListTile(
title: Text(item.title),
subtitle: Text(item.subtitle),
trailing:
item.task.isComplete ? const Icon(Icons.check) : null,
),
(final EmptyItem item) => ListTile(
title: Text(item.title),
),
},
itemSeparatorBuilder:
(context, section, item, sectionIndex, itemIndex,
{required bool insideSection}) =>
const Divider(indent: 16),
);
完整示例代码
以下是完整的示例代码,展示了如何使用 list_view_with_data_source
插件来创建一个可过滤的任务列表。
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:list_view_with_data_source/list_view_with_data_source.dart';
// 定义任务和项目类
sealed class Task {
const Task({
required this.name,
required this.isComplete,
});
final String name;
final bool isComplete;
String get type;
}
class DesktopTask extends Task {
const DesktopTask({
required super.name,
required super.isComplete,
});
[@override](/user/override)
String get type => 'Desktop';
}
class HouseTask extends Task {
const HouseTask({
required super.name,
required super.isComplete,
});
[@override](/user/override)
String get type => 'House';
}
sealed class Project {
String get name;
List<Task> get tasks;
}
class FamilyBirthday extends Project {
[@override](/user/override)
String get name => 'family birthday';
[@override](/user/override)
List<Task> get tasks => const [
DesktopTask(name: 'Create guest list', isComplete: true),
DesktopTask(name: 'Send invitations', isComplete: false),
HouseTask(name: 'Clean living room', isComplete: false),
HouseTask(name: 'Clean kitchen', isComplete: true),
HouseTask(name: 'Decorate photos', isComplete: false)
];
}
class HandmadeSupportApp extends Project {
[@override](/user/override)
String get name => 'handmade support app';
[@override](/user/override)
List<Task> get tasks => const [
DesktopTask(name: 'Create project', isComplete: true),
DesktopTask(name: 'Create tasks', isComplete: true),
DesktopTask(name: 'Create task list', isComplete: true),
DesktopTask(name: 'Create task detail', isComplete: false),
DesktopTask(name: 'Create task list item', isComplete: false),
DesktopTask(name: 'Create task detail item', isComplete: false),
];
}
class ReformKitchen extends Project {
[@override](/user/override)
String get name => 'reform kitchen';
[@override](/user/override)
List<Task> get tasks => const [
HouseTask(name: 'Plan new kitchen', isComplete: true),
HouseTask(name: 'Install new kitchen', isComplete: true),
];
}
// 定义项目节和项目节项
class ProjectSection extends Equatable {
const ProjectSection({
required this.project,
required this.itemCount,
});
final Project project;
final int itemCount;
String get title => project.name;
String get trailing =>
'${project.tasks.where((task) => task.isComplete).length}/${project.tasks.length}';
[@override](/user/override)
List<Object?> get props => [project, itemCount];
}
sealed class ProjectSectionItem {
const ProjectSectionItem();
String get title;
}
class TaskItem extends ProjectSectionItem {
const TaskItem({
required this.task,
});
final Task task;
[@override](/user/override)
String get title => task.name;
String get subtitle => task.type;
}
class EmptyItem extends ProjectSectionItem {
const EmptyItem();
[@override](/user/override)
String get title => 'No tasks';
}
// 主应用
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromARGB(255, 174, 181, 232),
),
),
home: const ProjectListScreen(),
);
}
}
typedef CustomDataSource = ListViewDataSource<ProjectSection, ProjectSectionItem>;
enum Filter {
all,
desktop,
house,
hideCompleted,
}
class ProjectListScreen extends StatefulWidget {
const ProjectListScreen({super.key});
[@override](/user/override)
State<ProjectListScreen> createState() => _ProjectListScreenState();
}
class _ProjectListScreenState extends State<ProjectListScreen> {
final ProjectListModel _model = ProjectListModel([
FamilyBirthday(),
HandmadeSupportApp(),
ReformKitchen(),
]);
Filter _filter = Filter.all;
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ListView With Data Source Example'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Text('Tasks filter:'),
const SizedBox(width: 8),
DropdownButton<Filter>(
value: _filter,
onChanged: (Filter? newValue) {
setState(() {
_filter = newValue!;
});
},
items: Filter.values
.map<DropdownMenuItem<Filter>>((Filter value) {
return DropdownMenuItem<Filter>(
value: value,
child: Text(
value.name,
style: const TextStyle(fontSize: 20),
),
);
}).toList(),
),
],
),
),
Expanded(
child: ListViewWithDataSource(
dataSource: _model.dataSourceFor(_filter),
itemBuilder:
(context, section, item, sectionIndex, itemIndex) =>
switch (item) {
(final TaskItem item) => ListTile(
title: Text(item.title),
subtitle: Text(item.subtitle),
trailing:
item.task.isComplete ? const Icon(Icons.check) : null,
),
(final EmptyItem item) => ListTile(
title: Text(item.title),
),
},
sectionHeaderBuilder: (context, section, sectionIndex) =>
ListTile(
title: Text(
section.title,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
sectionFooterBuilder: (context, section, sectionIndex) =>
0 < section.itemCount
? Row(
children: [
const Spacer(),
Text(section.trailing),
const SizedBox(width: 16),
],
)
: null,
itemSeparatorBuilder:
(context, section, item, sectionIndex, itemIndex,
{required bool insideSection}) =>
const Divider(indent: 16),
sectionSeparatorBuilder: (context, section, sectionIndex) =>
const Padding(
padding: EdgeInsets.only(top: 16),
child: Divider(),
),
),
),
],
));
}
}
class ProjectListModel {
ProjectListModel(this.projects);
final List<Project> projects;
CustomDataSource dataSourceFor(
Filter filter,
) {
switch (filter) {
case Filter.all:
final dataSource = CustomDataSource();
for (final project in projects) {
dataSource.appendItems(
ProjectSection(project: project, itemCount: project.tasks.length),
project.tasks.map((e) => TaskItem(task: e)).toList(),
);
}
return dataSource;
case Filter.desktop:
final dataSource = CustomDataSource();
for (final project in projects) {
final desktopTasks = project.tasks.whereType<DesktopTask>().toList();
final section =
ProjectSection(project: project, itemCount: desktopTasks.length);
dataSource.addSection(section);
if (desktopTasks.isNotEmpty) {
dataSource.appendItems(
section, desktopTasks.map((e) => TaskItem(task: e)).toList());
} else {
dataSource.appendItem(section, const EmptyItem());
}
}
return dataSource;
case Filter.house:
final dataSource = CustomDataSource();
for (final project in projects) {
final houseTasks = project.tasks.whereType<HouseTask>().toList();
final section =
ProjectSection(project: project, itemCount: houseTasks.length);
dataSource.addSection(section);
if (houseTasks.isNotEmpty) {
dataSource.appendItems(
section, houseTasks.map((e) => TaskItem(task: e)).toList());
} else {
dataSource.appendItem(section, const EmptyItem());
}
}
return dataSource;
case Filter.hideCompleted:
final dataSource = CustomDataSource();
for (final project in projects) {
final todo = project.tasks.where((e) => !e.isComplete).toList();
final section =
ProjectSection(project: project, itemCount: todo.length);
dataSource.addSection(section);
if (todo.isNotEmpty) {
dataSource.appendItems(
section, todo.map((e) => TaskItem(task: e)).toList());
} else {
dataSource.appendItem(section, const EmptyItem());
}
}
return dataSource;
}
}
}
更多关于Flutter数据驱动列表插件list_view_with_data_source的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter数据驱动列表插件list_view_with_data_source的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter中使用list_view_with_data_source
插件(假设这是一个自定义的或假想的插件,因为Flutter官方并没有直接名为list_view_with_data_source
的插件)的示例代码。这个示例将展示如何创建一个数据驱动的ListView。
由于list_view_with_data_source
这个名称并不是官方的,我会基于一个常见的数据驱动ListView的场景来编写代码。在这个场景中,我们将使用一个自定义的DataSource
类来管理数据,并在Flutter的ListView.builder
中展示这些数据。
首先,假设我们有一个DataSource
类,它负责提供数据列表:
class DataSource {
List<String> _items = [];
DataSource() {
// 初始化一些数据
_items.addAll(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']);
}
// 获取数据列表
List<String> getItems() {
return _items;
}
// 添加一个新项目
void addItem(String item) {
_items.add(item);
}
}
接下来,在Flutter应用中,我们将使用这个DataSource
来构建一个数据驱动的ListView:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DataSource _dataSource = DataSource();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Data Driven ListView'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _dataSource.getItems().length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_dataSource.getItems()[index]),
);
},
),
),
ElevatedButton(
onPressed: () {
setState(() {
_dataSource.addItem('New Item');
});
},
child: Text('Add Item'),
),
],
),
),
);
}
}
在这个示例中,我们做了以下几件事:
- 创建了一个
DataSource
类来管理数据列表。 - 在
MyHomePage
类中实例化了一个DataSource
对象。 - 使用
ListView.builder
来构建列表视图,它从DataSource
中获取数据。 - 添加了一个按钮,当点击按钮时,会向
DataSource
中添加一个新项目,并通过调用setState
来刷新UI。
这个示例展示了如何在Flutter中实现一个数据驱动的ListView,尽管没有直接使用名为list_view_with_data_source
的插件,但这种方法是通用的,并且可以根据需要扩展到更复杂的场景。如果你有一个特定的list_view_with_data_source
插件,并且需要更具体的代码示例,请提供更多关于该插件的信息。