Flutter数据驱动列表插件list_view_with_data_source的使用

Flutter数据驱动列表插件list_view_with_data_source的使用

Pub Version GitHub Workflow Status (with event)

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

定义 ProjectSectionProjectSectionItem 类,这些类将用于存储数据源中的数据。

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

1 回复

更多关于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'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们做了以下几件事:

  1. 创建了一个DataSource类来管理数据列表。
  2. MyHomePage类中实例化了一个DataSource对象。
  3. 使用ListView.builder来构建列表视图,它从DataSource中获取数据。
  4. 添加了一个按钮,当点击按钮时,会向DataSource中添加一个新项目,并通过调用setState来刷新UI。

这个示例展示了如何在Flutter中实现一个数据驱动的ListView,尽管没有直接使用名为list_view_with_data_source的插件,但这种方法是通用的,并且可以根据需要扩展到更复杂的场景。如果你有一个特定的list_view_with_data_source插件,并且需要更具体的代码示例,请提供更多关于该插件的信息。

回到顶部