Flutter可选中拖拽列表插件selectable_draggable_listbox的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter可选中拖拽列表插件selectable_draggable_listbox的使用

插件简介

selectable_draggable_listbox 是一个功能强大的Flutter插件,支持多选、拖拽和排序功能。它允许你将数据绑定到自定义的小部件列表中,并且可以轻松实现多个列表之间的拖拽操作。

主要特性

  • 绑定数据到列表视图
    • 简单的文本项小部件
    • 可自定义的模板化列表项小部件
  • 多选功能
    • 使用Shift点击选择连续项
    • 使用Ctrl/Cmd点击选择单个项
    • 支持单选模式
  • 可排序
    • 点击并拖动“三条线”图标重新排序项
  • 拖拽和放置
    • 在不同列表之间拖拽项
    • 每个列表可以独立设置为可拖拽、可放置或两者兼有

Feature Demo

开始使用

1. 添加依赖

你可以通过以下两种方式添加 selectable_draggable_listbox 到你的项目中:

  • 运行命令:

    flutter pub add selectable_draggable_listbox
    
  • 或者编辑 pubspec.yaml 文件,然后运行 flutter pub get

    dependencies:
      selectable_draggable_listbox: ^latest_version
    

2. 导入包

在你的Dart文件中导入插件:

import 'package:selectable_draggable_listbox/selectable_draggable_listbox.dart';

使用示例

下面是一个完整的示例代码,展示了如何使用 selectable_draggable_listbox 创建一个包含购物清单和最近购买清单的界面。这个示例包括多选、排序和拖拽功能。

示例代码

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:selectable_draggable_listbox/selectable_draggable_listbox.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Selectable Draggable Listbox Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Selectable Draggable Listbox Demo'),
    );
  }
}

class GroceryItem {
  final String name;

  GroceryItem({
    required this.name,
  });
}

class RecentGroceryItem extends GroceryItem {
  DateTime lastBought;

  String get lastBoughtShortDtTm =>
      DateFormat('MM/dd/yyyy kk:mm').format(lastBought);

  RecentGroceryItem({
    required super.name,
    required this.lastBought,
  });
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            // 购物清单:支持多选、排序和从该列表拖拽
            GroceryListWidget(),

            // 最近购买清单:支持单选、自定义模板和拖拽到该列表
            RecentListWidget(),
          ],
        ),
      ),
    );
  }
}

class GroceryListWidget extends StatefulWidget {
  const GroceryListWidget({
    super.key,
  });

  [@override](/user/override)
  State<GroceryListWidget> createState() => _GroceryListWidgetState();
}

class _GroceryListWidgetState extends State<GroceryListWidget> {
  final _groceryList = [
    GroceryItem(name: 'Apples'),
    GroceryItem(name: 'Bananas'),
    GroceryItem(name: 'Milk'),
    GroceryItem(name: 'Cheese'),
    GroceryItem(name: 'Bread'),
  ].forListbox().toList();

  [@override](/user/override)
  Widget build(BuildContext context) {
    /// 创建简单的列表项模板
    /// 展示带有编号前缀的项
    /// 当拖拽时,移除编号前缀
    SimpleListboxItem<GroceryItem> makeItemTemplate(
      int index,
      ListboxEventManager eventManager,
      ListItem<GroceryItem> item,
      void Function(ListItem<GroceryItem>)? onSelect,
      bool isDragging,
    ) {
      return SimpleListboxItem(
        key: Key('$index'),
        eventManager: eventManager,
        item: item,
        label: isDragging ? item.data.name : '${index + 1}. ${item.data.name}',
        onSelect: onSelect,
        isDragging: isDragging,
      );
    }

    void onSelect(Iterable<ListItem<GroceryItem>> itemsSelected) {
      debugPrint(
          'Selected: ${itemsSelected.map((e) => e.data.name).join(',')}');
      setState(() {
        for (var item in _groceryList) {
          item.isSelected = itemsSelected.contains(item);
        }
      });
    }

    void onReorder(int oldIndex, int newIndex) {
      debugPrint('Moving item from $oldIndex to $newIndex');
      setState(() {
        // 使用扩展方法移动项
        _groceryList.move(oldIndex, newIndex);
      });
    }

    return Flexible(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Text(
              'Grocery List',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const Text('Features: Multi-select, Reorder, Drag From'),
            Expanded(
              child: Builder(
                builder: (context) {
                  return Listbox(
                    // 仅在调试时需要键来标识交互的列表
                    key: const Key('GroceryList'),
                    items: _groceryList,
                    onSelect: onSelect,
                    onReorder: onReorder,
                    itemTemplate: (context, eventManager, index, item, onSelect) =>
                        makeItemTemplate(index, eventManager, item, onSelect, false),
                    dragTemplate: (context, eventManager, index, item) =>
                        makeItemTemplate(index, eventManager, item, null, true),
                    // 启用调试信息
                    enableDebug: true,
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class RecentListWidget extends StatefulWidget {
  const RecentListWidget({
    super.key,
  });

  [@override](/user/override)
  State<RecentListWidget> createState() => _RecentListWidgetState();
}

class _RecentListWidgetState extends State<RecentListWidget> {
  final _recentList = [
    RecentGroceryItem(
      name: 'Apples',
      lastBought: DateTime(2024, 3, 3, 13, 22, 33),
    ),
    RecentGroceryItem(
      name: 'Bread',
      lastBought: DateTime(2024, 3, 4, 10, 14, 12),
    )
  ].forListbox().toList();

  [@override](/user/override)
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;

    /// 创建更复杂的列表项模板
    /// 展示带有日期标记的行
    TemplatedListboxItem<RecentGroceryItem> makeItemTemplate(
      int index,
      ListboxEventManager eventManager,
      ListItem<RecentGroceryItem> item,
      String label,
      void Function(ListItem<RecentGroceryItem>)? onSelect,
      bool isDragPlaceholder,
    ) {
      return TemplatedListboxItem(
        key: Key('$index'),
        item: item,
        childTemplate: (context, item) {
          return Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(label),
                if (!isDragPlaceholder)
                  Badge(
                    label: Text(item.data.lastBoughtShortDtTm),
                  ),
              ],
            ),
          );
        },
        eventManager: eventManager,
        onSelect: onSelect,
        customDecoration: isDragPlaceholder
            ? BoxDecoration(
                color: colorScheme.primaryContainer,
                borderRadius: const BorderRadius.all(
                  Radius.circular(5),
                ),
              )
            : null,
      );
    }

    void onSelect(Iterable<ListItem<RecentGroceryItem>> itemsSelected) {
      debugPrint(
          'Selected: ${itemsSelected.map((e) => e.data.name).join(',')}');
      setState(() {
        for (var item in _recentList) {
          item.isSelected = itemsSelected.contains(item);
        }
      });
    }

    void onDrop(Iterable<ListItem<RecentGroceryItem>> itemsDropped, int index) {
      debugPrint(
          'Dropped items ${itemsDropped.map((e) => e.data.name).join(',')} into index $index');

      // 避免添加重复项
      final existingNames = _recentList.map((i) => i.data.name);

      // 将拖拽的项插入到列表中
      final itemsToInsert = itemsDropped
          .where((i) => !existingNames.contains(i.data.name))
          .toList();
      _recentList.insertAll(index, itemsToInsert);
    }

    return Flexible(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Text(
              'Recently Bought',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const Text('Features: Single-select, Customized Template, Drag To'),
            Expanded(
              child: Builder(
                builder: (context) {
                  return Listbox(
                    // 仅在调试时需要键来标识交互的列表
                    key: const Key('RecentList'),
                    items: _recentList,
                    onSelect: onSelect,
                    onDrop: onDrop,
                    dragDropTransform: (input) {
                      if (input is GroceryItem || input is RecentGroceryItem) {
                        return RecentGroceryItem(
                            name: input.name, lastBought: DateTime.now());
                      } else {
                        throw Exception(
                            'Cannot accept items of type ${input.runtimeType}');
                      }
                    },
                    itemTemplate: (context, eventManager, index, item, onSelect) =>
                        makeItemTemplate(index, eventManager, item, item.data.name, onSelect, false),
                    dropPlaceholderTemplate: (context, eventManager, index, item, itemsToBeDropped) {
                      var itemsLength = itemsToBeDropped.length;

                      // 根据拖拽的项数量调整占位符标签
                      var label = itemsLength > 1
                          ? '$itemsLength new items...'
                          : itemsLength > 0
                              ? itemsToBeDropped.first.data.name
                              : '';
                      return makeItemTemplate(
                          index, eventManager, item, label, null, true);
                    },
                    // 关闭多选功能
                    disableMultiSelect: true,
                    // 启用调试信息
                    enableDebug: true,
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter可选中拖拽列表插件selectable_draggable_listbox的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter可选中拖拽列表插件selectable_draggable_listbox的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,selectable_draggable_listbox 是一个在 Flutter 中实现可选中拖拽列表的插件。下面是一个简单的代码示例,展示如何使用这个插件来创建一个可选中且可拖拽的列表。

首先,确保你已经在 pubspec.yaml 文件中添加了 selectable_draggable_listbox 依赖:

dependencies:
  flutter:
    sdk: flutter
  selectable_draggable_listbox: ^最新版本号 # 请替换为实际的最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,是一个示例代码,展示如何使用 SelectableDraggableListBox

import 'package:flutter/material.dart';
import 'package:selectable_draggable_listbox/selectable_draggable_listbox.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Selectable Draggable ListBox Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> items = List<String>.generate(10, (i) => "Item $i");

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Selectable Draggable ListBox Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: SelectableDraggableListBox<String>(
          items: items,
          selectedColor: Colors.lightBlueAccent,
          unselectedColor: Colors.white,
          selectedTextStyle: TextStyle(color: Colors.white),
          unselectedTextStyle: TextStyle(color: Colors.black),
          onSelectedItemsChanged: (selectedItems) {
            setState(() {
              // 这里可以处理选中的项目,比如保存到状态或者进行其他逻辑处理
              print("Selected items: $selectedItems");
            });
          },
          onDragEnded: (fromIndex, toIndex, item) {
            setState(() {
              // 更新列表中的项目顺序
              if (fromIndex < toIndex) {
                items.insert(toIndex, items.removeAt(fromIndex));
              } else if (fromIndex > toIndex) {
                items.insert(toIndex, items.removeAt(fromIndex));
              }
              // 这里可以添加其他逻辑处理,比如更新数据源
            });
          },
        ),
      ),
    );
  }
}

代码解释

  1. 依赖导入:确保导入了 selectable_draggable_listbox 插件。
  2. 主应用结构:定义了一个简单的 Flutter 应用,包含一个 MaterialApp 和一个 MyHomePage 页面。
  3. 状态管理MyHomePage 是一个有状态组件,用于管理列表项的状态。
  4. 列表数据items 列表包含了初始的10个项目。
  5. UI构建
    • 使用 SelectableDraggableListBox 组件来构建可选中且可拖拽的列表。
    • selectedColorunselectedColor 用于设置选中项和未选中项的背景颜色。
    • selectedTextStyleunselectedTextStyle 用于设置选中项和未选中项的文本样式。
    • onSelectedItemsChanged 回调用于处理选中项的变化。
    • onDragEnded 回调用于处理拖拽结束后的逻辑,包括更新列表的顺序。

这样,你就可以创建一个可选中且可拖拽的列表,并处理用户交互事件。根据实际需求,你可以进一步扩展和定制这个示例。

回到顶部