🎯 Flutter 拖拽选择组件:flutter_drag_selector —— 像选文件一样选择列表项

在 Flutter 开发桌面端应用中,我们经常需要在一个滚动列表(如 ListViewGridViewWrap)中让用户批量选择多个项目。传统的做法是点击每个项目逐一选中,或使用"全选"按钮,但这在项目数量多、布局密集的场景下体验不佳。

flutter_drag_selector 正是为解决这一痛点而生的轻量级 Flutter 插件。它让你像在桌面系统中用鼠标框选文件一样,通过拖拽鼠标(或手指)划出一个区域,自动选中该区域内的所有子组件,大幅提升交互效率。

✨ 核心功能概览

  • 拖拽框选:按住鼠标左键(或触摸屏长按)并拖动,实时绘制一个半透明选择框。
  • 自动识别 :自动检测拖拽区域内所有被 SelectableItem 包裹的子组件,并动态选中/取消选中。
  • 可定制:支持自定义选择区域的样式(颜色、圆角、边框)、选中状态回调、滚动容器控制等。

🛠️ 如何使用?

1. 添加依赖

在你的 pubspec.yaml 文件中添加:

yaml 复制代码
dependencies:
  flutter_drag_selector: ^latest

2. 包装你的列表

将你的滚动容器(如 SingleChildScrollView + Wrap)用 CursorSelectorWidget 包裹,并用 SelectableItem 包裹每一个可选项目。

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_drag_selector/flutter_drag_selector.dart';

class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _list = List.generate(50, (i) => i);
  final _controller = StreamController<(Key?, bool)>.broadcast(); // 用于更新 UI 状态
  final scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    _controller.close();
    scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Widget buildBox(int index) {
      final id = ValueKey<int>(index);
      return StreamBuilder<(Key?, bool)>(
        stream: _controller.stream.where((e) => e.$1 == id),
        builder: (ctx, snapshot) {
          return SelectableItem(
            key: id,
            child: GestureDetector(
              onTap: () {
                debugPrint('tap -> $index');
              },
              child: Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(
                  color: index % 2 == 0 ? Colors.yellow : Colors.lightBlueAccent,
                ),
                alignment: Alignment.center,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('$index', style: const TextStyle(fontSize: 20)),
                    const SizedBox(width: 20),
                    Icon(
                      (snapshot.data?.$2 ?? false)
                          ? Icons.check_box
                          : Icons.check_box_outline_blank,
                      size: 40,
                      color: Colors.red,
                    )
                  ],
                ),
              ),
            ),
          );
        },
      );
    }

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('拖拽选择示例')),
        body: CursorSelectorTheme(
          data: CursorSelectorThemeData(
            selectedAreaDecoration: BoxDecoration(
              color: Colors.blue.withOpacity(0.4),
              borderRadius: BorderRadius.circular(10),
            ),
          ),
          child: CursorSelectorWidget(
            scrollController: scrollController, // 控制滚动
            selectedChangedCallback: (selection) {
              // 选中状态变更回调,用于更新 UI
              _controller.add(selection);
            },
            child: SingleChildScrollView(
              controller: scrollController,
              child: Wrap(
                spacing: 10,
                runSpacing: 10,
                children: _list.map<Widget>(buildBox).toList(),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

3. 效果说明

  • 用户按住鼠标左键(或在移动端长按)并拖动,会看到一个蓝色半透明矩形框跟随鼠标移动。
  • 每次SelectableItem的选中状态变化,都会通过 selectedChangedCallback 回调返回 (Key, isSelected),开发者可据此更新数据模型或 UI。

🎨 自定义主题

你可以通过 CursorSelectorThemeCursorSelectorThemeData 自定义选择框的视觉样式:

dart 复制代码
CursorSelectorTheme(
  data: CursorSelectorThemeData(
    selectedAreaDecoration: BoxDecoration(
      color: Colors.green.withOpacity(0.3),
      border: Border.all(color: Colors.green, width: 2),
      borderRadius: BorderRadius.circular(8),
    )
  ),
  child: CursorSelectorWidget(...),
)

结语

如有设计不佳的点,欢迎指出。

👉 GitHub 地址github.com/bladeofgod/...

👉 Pub 地址pub.dev/packages/fl...

相关推荐
风度前端35 分钟前
用了都说好的 uniapp 路由框架
前端
冴羽36 分钟前
2026 年 Web 前端开发的 8 个趋势!
前端·javascript·vue.js
码银44 分钟前
ruoyi的前端(vue)新增的时候给字典设置默认值 但不能正常
前端
程序员老刘1 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
凌览1 小时前
别再死磕 Nginx!http-proxy-middleware 低配置起飞
前端·后端
EndingCoder2 小时前
类的继承和多态
linux·运维·前端·javascript·ubuntu·typescript
用户47949283569152 小时前
React 终于出手了:彻底终结 useEffect 的"闭包陷阱"
前端·javascript·react.js
程序员猫哥2 小时前
前端开发,一句话生成网站
前端
Younglina2 小时前
一个纯前端的网站集合管理工具
前端·vue.js·chrome
木头程序员2 小时前
前端(包含HTML/JavaScript/DOM/BOM/jQuery)基础-暴力复习篇
开发语言·前端·javascript·ecmascript·es6·jquery·html5