🎯 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...

相关推荐
Keely402852 小时前
Claude 配置使用墨刀MCP(modao-proto-mcp)
前端·aigc·claude
少卿2 小时前
从零构建 React Native 导航体系-React Navigation
前端·react native
Wect2 小时前
学习React-DnD:实现 TodoList 简单拖拽功能
前端·react.js
前端小书生2 小时前
Google Map、Solar Api
前端·react.js·google
毕设十刻3 小时前
基于Vue的售票系统开发3g480(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
行走的陀螺仪3 小时前
前端CI/CD 流程
前端·ci/cd·工程化·自动化构建
裕波3 小时前
前端,不止于 AI。12 月 20 日,FEDAY 2025,长沙见!
前端
excel3 小时前
使用 Canvas 实现扫描效果:宽度计算、透明度控制与旋转
前端
MC丶科3 小时前
Spring Boot + Vue 实现一个在线商城(商品展示、购物车、订单)!从零到一完整项目
前端·vue.js·spring boot