Flutter多级联动下拉选择插件multi_dropdown的使用
Flutter多级联动下拉选择插件multi_dropdown的使用
简介
multi_dropdown 是一个功能强大且可高度定制的Flutter插件,用于管理并搜索下拉列表中的多个项目。你可以根据需要自定义下拉框样式,并通过控制器以编程方式控制下拉框。
安装
在 pubspec.yaml 文件中添加依赖:
dependencies:
multi_dropdown: ^3.0.0 # 根据实际版本号进行修改
然后执行 flutter pub get 命令来安装包。
使用方法
引入包
在 Dart 文件顶部引入 multi_dropdown 包:
import 'package:multi_dropdown/multi_dropdown.dart';
创建 MultiDropdown 组件
以下是一个完整的示例代码,展示了如何创建和配置 MultiDropdown<User> 组件。在这个例子中,我们定义了一个简单的用户模型 User,并用它作为下拉选项的值类型。
import 'package:flutter/material.dart';
import 'package:multi_dropdown/multi_dropdown.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Multiselect dropdown demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const MyHomePage(),
);
}
}
class User {
final String name;
final int id;
User({required this.name, required this.id});
@override
String toString() {
return 'User(name: $name, id: $id)';
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
final controller = MultiSelectController<User>();
@override
Widget build(BuildContext context) {
var items = [
DropdownItem(label: 'Nepal', value: User(name: 'Nepal', id: 1)),
DropdownItem(label: 'Australia', value: User(name: 'Australia', id: 6)),
DropdownItem(label: 'India', value: User(name: 'India', id: 2)),
DropdownItem(label: 'China', value: User(name: 'China', id: 3)),
DropdownItem(label: 'USA', value: User(name: 'USA', id: 4)),
DropdownItem(label: 'UK', value: User(name: 'UK', id: 5)),
DropdownItem(label: 'Germany', value: User(name: 'Germany', id: 7)),
DropdownItem(label: 'France', value: User(name: 'France', id: 8)),
];
return Scaffold(
appBar: AppBar(
title: const Text('MultiDropdown Example'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MultiDropdown<User>(
items: items,
controller: controller,
enabled: true,
searchEnabled: true,
chipDecoration: const ChipDecoration(
backgroundColor: Colors.yellow,
wrap: true,
runSpacing: 2,
spacing: 10,
),
fieldDecoration: FieldDecoration(
hintText: 'Countries',
hintStyle: const TextStyle(color: Colors.black87),
prefixIcon: const Icon(Icons.flag),
showClearIcon: false,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.grey),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Colors.black87,
),
),
),
dropdownDecoration: const DropdownDecoration(
marginTop: 2,
maxHeight: 500,
header: Padding(
padding: EdgeInsets.all(8),
child: Text(
'Select countries from the list',
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
dropdownItemDecoration: DropdownItemDecoration(
selectedIcon: const Icon(Icons.check_box, color: Colors.green),
disabledIcon: const Icon(Icons.lock, color: Colors.grey),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select a country';
}
return null;
},
onSelectionChange: (selectedItems) {
debugPrint("OnSelectionChange: $selectedItems");
},
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
children: [
ElevatedButton(
onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
final selectedItems = controller.selectedItems;
debugPrint(selectedItems.toString());
}
},
child: const Text('Submit'),
),
ElevatedButton(
onPressed: () {
controller.selectAll();
},
child: const Text('Select All'),
),
ElevatedButton(
onPressed: () {
controller.clearAll();
},
child: const Text('Unselect All'),
),
ElevatedButton(
onPressed: () {
controller.addItems([
DropdownItem(
label: 'France',
value: User(name: 'France', id: 8)),
]);
},
child: const Text('Add Items'),
),
ElevatedButton(
onPressed: () {
controller.selectWhere((element) =>
element.value.id == 1 ||
element.value.id == 2 ||
element.value.id == 3);
},
child: const Text('Select Where'),
),
ElevatedButton(
onPressed: () {
controller.selectAtIndex(0);
},
child: const Text('Select At Index'),
),
ElevatedButton(
onPressed: () {
controller.openDropdown();
},
child: const Text('Open/Close dropdown'),
),
],
)
],
),
),
),
);
}
}
参数说明
MultiDropdown
| Parameter | Type | Description | Default |
|---|---|---|---|
| items | List<DropdownItem<T>> | 下拉项列表 | Required |
| singleSelect | bool | 是否为单选模式 | false |
| chipDecoration | ChipDecoration | 芯片装饰配置 | ChipDecoration() |
| fieldDecoration | FieldDecoration | 字段装饰配置 | FieldDecoration() |
| dropdownDecoration | DropdownDecoration | 下拉菜单装饰配置 | DropdownDecoration() |
| searchDecoration | SearchFieldDecoration | 搜索字段装饰配置 | SearchFieldDecoration() |
| dropdownItemDecoration | DropdownItemDecoration | 下拉项装饰配置 | DropdownItemDecoration() |
| itemBuilder | DropdownItemBuilder<T>? | 下拉项构建器 | null |
| selectedItemBuilder | SelectedItemBuilder<T>? | 已选项构建器 | null |
| itemSeparator | Widget? | 下拉项分隔符 | null |
| validator | String? Function(List<DropdownItem<T>>) | 验证函数 | null |
| autovalidateMode | AutovalidateMode | 自动验证模式 | AutovalidateMode.disabled |
| controller | MultiSelectController<T>? | 控制器 | null |
| maxSelections | int | 最大选择数量 | 0 |
| enabled | bool | 是否启用 | true |
| searchEnabled | bool | 是否启用搜索 | false |
| focusNode | FocusNode? | 焦点节点 | null |
| future | FutureRequest<List<DropdownItem<T>>>? | 数据获取的异步请求 | null |
| onSelectionChange | OnSelectionChanged<T>? | 选择变化回调 | null |
| closeOnBackButton | bool | 返回按钮关闭下拉菜单 | false |
| onSearchChange | ValueChanged<String>? | 搜索文本变化回调 | null |
DropdownItem
| Parameter | Type | Description | Default |
|---|---|---|---|
| label | String | 下拉项标签 | Required |
| value | T | 下拉项关联值 | Required |
| disabled | bool | 是否禁用 | false |
| selected | bool | 是否已选中 | false |
ChipDecoration
| Parameter | Type | Description | Default |
|---|---|---|---|
| deleteIcon | Icon | 删除芯片图标 | Icon(Icons.close) |
| backgroundColor | Color | 背景色 | Colors.blue |
| labelStyle | TextStyle | 标签样式 | TextStyle() |
| padding | EdgeInsets | 内边距 | EdgeInsets.all(8.0) |
| border | BoxBorder | 边框 | Border() |
| spacing | double | 间距 | 8.0 |
| runSpacing | double | 行间距 | 8.0 |
| borderRadius | BorderRadiusGeometry | 圆角半径 | BorderRadius.circular(12) |
| wrap | bool | 是否换行 | true |
FieldDecoration
| Parameter | Type | Description | Default |
|---|---|---|---|
| labelText | String? | 标签文本 | null |
| hintText | String? | 提示文本 | null |
| border | InputBorder? | 边框 | null |
| focusedBorder | InputBorder? | 聚焦时边框 | null |
| disabledBorder | InputBorder? | 禁用时边框 | null |
| errorBorder | InputBorder? | 错误时边框 | null |
| suffixIcon | Widget? | 后缀图标 | Icon(Icons.arrow_drop_down) |
| prefixIcon | Widget? | 前缀图标 | null |
| labelStyle | TextStyle? | 标签样式 | null |
| hintStyle | TextStyle? | 提示样式 | null |
| borderRadius | double | 圆角半径 | 8.0 |
| animateSuffixIcon | bool | 是否动画后缀图标 | true |
| padding | EdgeInsets? | 内边距 | null |
| showClearIcon | bool | 是否显示清除图标 | true |
DropdownDecoration
| Parameter | Type | Description | Default |
|---|---|---|---|
| backgroundColor | Color | 背景色 | Colors.white |
| elevation | double | 抬升高度 | 1.0 |
| maxHeight | double | 最大高度 | 400.0 |
| borderRadius | BorderRadius | 圆角半径 | BorderRadius.circular(12.0) |
| marginTop | double | 上边距 | 0.0 |
| header | Widget? | 头部组件 | null |
| footer | Widget? | 尾部组件 | null |
SearchFieldDecoration
| Parameter | Type | Description | Default |
|---|---|---|---|
| hintText | String | 提示文本 | ‘Search’ |
| border | InputBorder? | 边框 | null |
| focusedBorder | InputBorder? | 聚焦时边框 | null |
| searchIcon | Icon | 搜索图标 | Icon(Icons.search) |
DropdownItemDecoration
| Parameter | Type | Description | Default |
|---|---|---|---|
| backgroundColor | Color? | 背景色 | null |
| disabledBackgroundColor | Color? | 禁用背景色 | null |
| selectedBackgroundColor | Color? | 选中背景色 | null |
| selectedTextColor | Color? | 选中文本颜色 | null |
| textColor | Color? | 文本颜色 | null |
| disabledTextColor | Color? | 禁用文本颜色 | null |
| selectedIcon | Icon? | 选中图标 | null |
| disabledIcon | Icon? | 禁用图标 | null |
控制器(Controller)
MultiSelectController<T> 提供了对下拉框的选择、取消选择、清空选择等操作的支持。以下是常用的控制器方法:
clearAll():清空所有选择。selectWhere(bool Function(DropdownItem<T>) predicate):根据条件选择项。setItems(List<DropdownItem<T>> options):设置下拉项。addItem(DropdownItem<T> option):添加单项。addItems(List<DropdownItem<T>> options):添加多项。disableWhere(bool Function(DropdownItem<T>) predicate):根据条件禁用项。selectAll():全选。selectAtIndex(int index):选择指定索引项。deselectWhere(bool Function(DropdownItem<T>) predicate):根据条件取消选择项。items:获取所有项。selectedItems:获取已选项。disabledItems:获取禁用项。openDropdown():打开下拉菜单。closeDropdown():关闭下拉菜单。
通过以上内容,你应该能够轻松地将 multi_dropdown 插件集成到你的 Flutter 应用中,并实现多级联动下拉选择功能。如果你有任何问题或需要进一步的帮助,请随时查阅官方文档或访问 GitHub 仓库获取更多信息。
更多关于Flutter多级联动下拉选择插件multi_dropdown的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter多级联动下拉选择插件multi_dropdown的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter中使用multi_dropdown插件来实现多级联动下拉选择的示例代码。multi_dropdown插件允许你创建多层级的下拉选择菜单,其中每一级的选项都依赖于前一级的选择。
首先,确保你已经在pubspec.yaml文件中添加了multi_dropdown依赖:
dependencies:
flutter:
sdk: flutter
multi_dropdown: ^x.y.z # 请替换为最新版本号
然后,运行flutter pub get来安装依赖。
接下来,在你的Dart文件中使用MultiDropdown组件。以下是一个简单的示例,演示如何实现三级联动下拉选择:
import 'package:flutter/material.dart';
import 'package:multi_dropdown/multi_dropdown.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Multi Dropdown Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiDropdownDemo(),
);
}
}
class MultiDropdownDemo extends StatefulWidget {
@override
_MultiDropdownDemoState createState() => _MultiDropdownDemoState();
}
class _MultiDropdownDemoState extends State<MultiDropdownDemo> {
// 定义三级联动的数据
final List<Map<String, dynamic>> countryData = [
{
'name': 'USA',
'states': [
{'name': 'California', 'cities': ['Los Angeles', 'San Francisco']},
{'name': 'Texas', 'cities': ['Houston', 'Austin']},
],
},
{
'name': 'Canada',
'states': [
{'name': 'Ontario', 'cities': ['Toronto', 'Ottawa']},
{'name': 'Quebec', 'cities': ['Montreal', 'Quebec City']},
],
},
];
String? selectedCountry;
String? selectedState;
String? selectedCity;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Multi Dropdown Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Country:'),
SizedBox(height: 8.0),
MultiDropdown<String>(
items: countryData.map((e) => e['name'] as String).toList(),
onChanged: (value) {
setState(() {
selectedCountry = value;
selectedState = null;
selectedCity = null;
});
_updateStatesAndCities(value);
},
value: selectedCountry,
),
SizedBox(height: 16.0),
Text('State:'),
SizedBox(height: 8.0),
MultiDropdown<String>(
items: _getStates(selectedCountry ?? ''),
onChanged: (value) {
setState(() {
selectedState = value;
selectedCity = null;
});
_updateCities(value);
},
value: selectedState,
isEnabled: selectedCountry != null,
),
SizedBox(height: 16.0),
Text('City:'),
SizedBox(height: 8.0),
MultiDropdown<String>(
items: _getCities(selectedState ?? ''),
onChanged: (value) {
setState(() {
selectedCity = value;
});
},
value: selectedCity,
isEnabled: selectedState != null,
),
],
),
),
);
}
// 根据选择的国家更新州列表
void _updateStatesAndCities(String? country) {
List<String> states = countryData
.firstWhereOrNull((element) => element['name'] == country)
?.['states']
?.map((e) => e['name'] as String)
?.toList() ?? [];
setState(() {
// 这里实际上不需要重新设置selectedState和selectedCity为空,因为在onChanged中已经处理
// 但为了清晰起见,可以保留这行代码,确保当国家改变时,州和城市重置
if (selectedState != null && !_contains(states, selectedState!)) {
selectedState = null;
selectedCity = null;
}
});
}
// 根据选择的州更新城市列表
void _updateCities(String? state) {
List<String> cities = countryData
.firstWhereOrNull((element) => element['name'] == selectedCountry)
?.['states']
?.firstWhereOrNull((e) => e['name'] == state)
?.['cities']
?.map((e) => e as String)
?.toList() ?? [];
setState(() {
if (selectedCity != null && !_contains(cities, selectedCity!)) {
selectedCity = null;
}
});
}
// 获取州的列表
List<String> _getStates(String country) {
return countryData
.firstWhereOrNull((element) => element['name'] == country)
?.['states']
?.map((e) => e['name'] as String)
?.toList() ?? [];
}
// 获取城市的列表
List<String> _getCities(String state) {
return countryData
.firstWhereOrNull((element) => element['name'] == selectedCountry)
?.['states']
?.firstWhereOrNull((e) => e['name'] == state)
?.['cities']
?.map((e) => e as String)
?.toList() ?? [];
}
// 辅助函数,检查列表中是否包含某个元素
bool _contains(List<String> list, String item) {
return list.contains(item);
}
}
// 辅助扩展函数,用于处理可能为null的firstWhere结果
extension NullableFirstWhereExtension<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T element) test) {
try {
return firstWhere(test);
} catch (_) {
return null;
}
}
}
在这个示例中,我们定义了一个包含国家和州/城市数据的列表。我们使用MultiDropdown组件来创建三个级别的下拉选择菜单,每一级的选项都依赖于前一级的选择。通过onChanged回调,我们更新选中的值,并相应地更新下一级的选项列表。
注意,我们使用了一个辅助扩展函数firstWhereOrNull来处理可能为空的firstWhere结果,以避免在数据不匹配时抛出异常。

