Flutter可搜索下拉列表插件flutter_searchable_dropdown的使用
Flutter可搜索下拉列表插件flutter_searchable_dropdown的使用
插件简介
flutter_searchable_dropdown
是一个Flutter插件,允许用户通过输入关键字在对话框或菜单中以单选或多选的方式从列表中进行选择。该插件已在iOS、Android和Chrome平台上成功测试。
使用步骤
1. 添加依赖
首先,在项目的 pubspec.yaml
文件中添加插件依赖:
dependencies:
searchable_dropdown: ^latest_version # 替换为最新版本号
然后运行命令安装依赖包:
flutter packages get
2. 导入库
在Dart文件中导入插件:
import 'package:searchable_dropdown/searchable_dropdown.dart';
3. 单选构造函数
创建一个单选的可搜索下拉列表:
SearchableDropdown.single(
items: items, // DropdownMenuItem<T>类型的列表
value: selectedValue, // 当前选中的值
hint: "Select one", // 提示信息
searchHint: "Select one", // 搜索框提示信息
onChanged: (value) {
setState(() {
selectedValue = value;
});
}, // 选择改变时触发的回调
isExpanded: true, // 是否扩展整个宽度
)
4. 多选构造函数
创建一个多选的可搜索下拉列表:
SearchableDropdown.multiple(
items: items, // DropdownMenuItem<T>类型的列表
selectedItems: selectedItems, // 已选中的项索引列表
hint: Padding(
padding: const EdgeInsets.all(12.0),
child: Text("Select any"),
), // 提示信息
searchHint: "Select any", // 搜索框提示信息
onChanged: (value) {
setState(() {
selectedItems = value;
});
}, // 选择改变时触发的回调
closeButton: (selectedItems) {
return (selectedItems.isNotEmpty
? "Save ${selectedItems.length == 1 ? '"' + items[selectedItems.first].value.toString() + '"' : '(' + selectedItems.length.toString() + ')'}"
: "Save without selection");
}, // 关闭按钮文本
isExpanded: true, // 是否扩展整个宽度
)
完整示例代码
以下是一个完整的示例应用代码,展示了如何使用 flutter_searchable_dropdown
插件来创建不同的可搜索下拉列表:
import 'package:flutter/material.dart';
import 'package:searchable_dropdown/searchable_dropdown.dart';
class ExampleNumber {
int number;
static final Map<int, String> map = {
0: "zero",
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine",
10: "ten",
11: "eleven",
12: "twelve",
13: "thirteen",
14: "fourteen",
15: "fifteen",
};
String? get numberString {
return (map.containsKey(number) ? map[number] : "unknown");
}
ExampleNumber(this.number);
@override
String toString() {
return ("$number $numberString");
}
static List<ExampleNumber> get list {
return (map.keys.map((number) {
return (ExampleNumber(number));
})).toList();
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
bool asTabs = false;
String? selectedValue;
String preselectedValue = "dolor sit";
ExampleNumber? selectedNumber;
List<int> selectedItems = [];
final List<DropdownMenuItem> items = [];
static const String appTitle = "Search Choices demo";
final String loremIpsum =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
@override
void initState() {
String wordPair = "";
loremIpsum
.toLowerCase()
.replaceAll(",", "")
.replaceAll(".", "")
.split(" ")
.forEach((word) {
if (wordPair.isEmpty) {
wordPair = "$word ";
} else {
wordPair += word;
if (items.indexWhere((item) {
return (item.value == wordPair);
}) ==
-1) {
items.add(DropdownMenuItem(
value: wordPair,
child: Text(wordPair),
));
}
wordPair = "";
}
});
super.initState();
}
List<Widget> get appBarActions {
return ([
const Center(child: Text("Tabs:")),
Switch(
activeColor: Colors.white,
value: asTabs,
onChanged: (value) {
setState(() {
asTabs = value;
});
},
)
]);
}
@override
Widget build(BuildContext context) {
Map<String, Widget> widgets;
widgets = {
"Single dialog": SearchableDropdown.single(
items: items,
value: selectedValue,
hint: "Select one",
searchHint: "Select one",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
isExpanded: true,
),
"Multi dialog": SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: const Padding(
padding: EdgeInsets.all(12.0),
child: Text("Select any"),
),
searchHint: "Select any",
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
closeButton: (selectedItems) {
return (selectedItems.isNotEmpty
? "Save ${selectedItems.length == 1 ? '"${items[selectedItems.first].value}"' : '(${selectedItems.length})'}"
: "Save without selection");
},
isExpanded: true,
),
"Single done button dialog": SearchableDropdown.single(
items: items,
value: selectedValue,
hint: "Select one",
searchHint: "Select one",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
doneButton: "Done",
displayItem: (item, selected) {
return (Row(children: [
(selected)
? const Icon(
Icons.radio_button_checked,
color: Colors.grey,
)
: const Icon(
Icons.radio_button_unchecked,
color: Colors.grey,
),
const SizedBox(width: 7),
Expanded(
child: item,
),
]));
},
isExpanded: true,
),
"Multi custom display dialog": SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: const Padding(
padding: EdgeInsets.all(12.0),
child: Text("Select any"),
),
searchHint: "Select any",
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
displayItem: (item, selected) {
return (Row(children: [
selected
? const Icon(
Icons.check,
color: Colors.green,
)
: const Icon(
Icons.check_box_outline_blank,
color: Colors.grey,
),
const SizedBox(width: 7),
Expanded(
child: item,
),
]));
},
selectedValueWidgetFn: (item) {
return (Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(
color: Colors.brown,
width: 0.5,
),
),
margin: const EdgeInsets.all(12),
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(item.toString()),
))));
},
doneButton: (selectedItemsDone, doneContext) {
return (ElevatedButton(
onPressed: () {
Navigator.pop(doneContext);
setState(() {});
},
child: const Text("Save")));
},
closeButton: null,
style: const TextStyle(fontStyle: FontStyle.italic),
searchFn: (String keyword, items) {
List<int> ret = <int>[];
if (items != null && keyword.isNotEmpty) {
keyword.split(" ").forEach((k) {
int i = 0;
items.forEach((item) {
if (k.isNotEmpty &&
(item.value
.toString()
.toLowerCase()
.contains(k.toLowerCase()))) {
ret.add(i);
}
i++;
});
});
}
if (keyword.isEmpty) {
ret = Iterable<int>.generate(items.length).toList();
}
return (ret);
},
clearIcon: const Icon(Icons.clear_all),
icon: const Icon(Icons.arrow_drop_down_circle),
label: "Label for multi",
underline: Container(
height: 1.0,
decoration: const BoxDecoration(
border:
Border(bottom: BorderSide(color: Colors.teal, width: 3.0))),
),
iconDisabledColor: Colors.brown,
iconEnabledColor: Colors.indigo,
isExpanded: true,
),
"Multi select 3 dialog": SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select 3 items",
searchHint: "Select 3",
validator: (selectedItemsForValidator) {
if (selectedItemsForValidator.length != 3) {
return ("Must select 3");
}
return (null);
},
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
doneButton: (selectedItemsDone, doneContext) {
return (ElevatedButton(
onPressed: selectedItemsDone.length != 3
? null
: () {
Navigator.pop(doneContext);
setState(() {});
},
child: const Text("Save")));
},
closeButton: (selectedItems) {
return (selectedItems.length == 3 ? "Ok" : null);
},
isExpanded: true,
),
"Single menu": SearchableDropdown.single(
items: items,
value: selectedValue,
hint: "Select one",
searchHint: null,
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: false,
isExpanded: true,
menuConstraints: BoxConstraints.tight(const Size.fromHeight(350)),
),
"Multi menu": SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select any",
searchHint: "",
doneButton: "Close",
closeButton: const SizedBox.shrink(),
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
dialogBox: false,
isExpanded: true,
menuConstraints: BoxConstraints.tight(const Size.fromHeight(350)),
),
"Multi menu select all/none": SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select any",
searchHint: "Select any",
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
dialogBox: false,
closeButton: (selectedItemsClose) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ElevatedButton(
onPressed: () {
setState(() {
selectedItems.clear();
selectedItems.addAll(
Iterable<int>.generate(items.length).toList());
});
},
child: const Text("Select all")),
ElevatedButton(
onPressed: () {
setState(() {
selectedItems.clear();
});
},
child: const Text("Select none")),
],
);
},
isExpanded: true,
menuConstraints: BoxConstraints.tight(const Size.fromHeight(350)),
),
"Multi dialog select all/none without clear": SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select any",
searchHint: "Select any",
displayClearIcon: false,
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
dialogBox: true,
closeButton: (selectedItemsClose) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ElevatedButton(
onPressed: () {
setState(() {
selectedItems.clear();
selectedItems.addAll(
Iterable<int>.generate(items.length).toList());
});
},
child: const Text("Select all")),
ElevatedButton(
onPressed: () {
setState(() {
selectedItems.clear();
});
},
child: const Text("Select none")),
],
);
},
isExpanded: true,
),
"Single dialog custom keyboard": SearchableDropdown.single(
items: Iterable<int>.generate(20).toList().map((i) {
return (DropdownMenuItem(
value: i.toString(),
child: Text(i.toString()),
));
}).toList(),
value: selectedValue,
hint: "Select one number",
searchHint: "Select one number",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: true,
keyboardType: TextInputType.number,
isExpanded: true,
),
"Single dialog object": SearchableDropdown.single(
items: ExampleNumber.list.map((exNum) {
return (DropdownMenuItem(
value: exNum, child: Text(exNum.numberString ?? '')));
}).toList(),
value: selectedNumber,
hint: "Select one number",
searchHint: "Select one number",
onChanged: (value) {
setState(() {
selectedNumber = value;
});
},
dialogBox: true,
isExpanded: true,
),
"Single dialog overflow": SearchableDropdown.single(
items: const [
DropdownMenuItem(
value:
"way too long text for a smartphone at least one that goes in a normal sized pair of trousers but maybe not for a gigantic screen like there is one at my cousin's home in a very remote country where I wouldn't want to go right now",
child: Text(
"way too long text for a smartphone at least one that goes in a normal sized pair of trousers but maybe not for a gigantic screen like there is one at my cousin's home in a very remote country where I wouldn't want to go right now"),
)
],
value: "",
hint: "Select one",
searchHint: "Select one",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: true,
isExpanded: true,
),
"Single dialog readOnly": SearchableDropdown.single(
items: const [
DropdownMenuItem(
value: "one item",
child: Text("one item"),
)
],
value: "one item",
hint: "Select one",
searchHint: "Select one",
disabledHint: "Disabled",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: true,
isExpanded: true,
readOnly: true,
),
"Single dialog disabled": SearchableDropdown.single(
items: const [
DropdownMenuItem(
value: "one item",
child: Text("one item"),
)
],
value: "one item",
hint: "Select one",
searchHint: "Select one",
disabledHint: "Disabled",
onChanged: null,
dialogBox: true,
isExpanded: true,
),
"Historical example": SearchableDropdown(
items: items,
value: selectedValue,
hint: const Text('Select One'),
searchHint: const Text(
'Select One',
style: TextStyle(fontSize: 20),
),
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
isExpanded: true,
),
"Update value from outside the plugin": Column(
children: [
SearchableDropdown(
items: items,
value: selectedValue,
hint: const Text('Select One'),
searchHint: const Text(
'Select One',
style: TextStyle(fontSize: 20),
),
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
isExpanded: true,
),
TextButton(
child: Text("Select $preselectedValue"),
onPressed: () {
setState(() {
selectedValue = preselectedValue;
});
},
),
],
),
};
return MaterialApp(
home: asTabs
? DefaultTabController(
length: widgets.length,
child: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
actions: appBarActions,
bottom: TabBar(
isScrollable: true,
tabs: Iterable<int>.generate(widgets.length)
.toList()
.map((i) {
return (Tab(
text: (i + 1).toString(),
));
}).toList(), //widgets.keys.toList().map((k){return(Tab(text: k));}).toList(),
),
),
body: Container(
padding: const EdgeInsets.all(20),
child: TabBarView(
children: widgets
.map((k, v) {
return (MapEntry(
k,
SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: [
Text(k),
const SizedBox(
height: 20,
),
v,
]),
)));
})
.values
.toList(),
),
),
),
)
: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
actions: appBarActions,
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: widgets
.map((k, v) {
return (MapEntry(
k,
Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(
color: Colors.grey,
width: 1.0,
),
),
margin: const EdgeInsets.all(20),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text("$k:"),
v,
],
),
)))));
})
.values
.toList(),
),
),
),
);
}
}
总结
flutter_searchable_dropdown
提供了丰富的配置选项和灵活的API,可以帮助开发者快速构建出功能强大的可搜索下拉列表组件。无论是单选还是多选,都可以根据需求进行定制化开发。希望这个详细的指南能够帮助你更好地理解和使用这个插件。如果有任何问题或建议,欢迎访问 GitHub Issues 进行反馈。
更多关于Flutter可搜索下拉列表插件flutter_searchable_dropdown的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter可搜索下拉列表插件flutter_searchable_dropdown的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用flutter_searchable_dropdown
插件的示例代码。这个插件允许你创建一个可搜索的下拉列表,这在需要用户从大量选项中进行选择时非常有用。
首先,你需要在pubspec.yaml
文件中添加依赖项:
dependencies:
flutter:
sdk: flutter
flutter_searchable_dropdown: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖项。
接下来,你可以在你的Dart文件中使用flutter_searchable_dropdown
。以下是一个完整的示例,包括数据准备和UI构建:
import 'package:flutter/material.dart';
import 'package:flutter_searchable_dropdown/flutter_searchable_dropdown.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Searchable Dropdown Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// 示例数据
List<String> dropdownItems = [
'Apple',
'Banana',
'Cherry',
'Date',
'Elderberry',
'Fig',
'Grape',
// ...更多选项
];
String? selectedValue;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Searchable Dropdown Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Selected Item:',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 10),
Text(
selectedValue ?? 'None',
style: TextStyle(fontSize: 16),
),
SizedBox(height: 20),
SearchableDropdown.single(
isExpanded: true,
value: selectedValue,
hint: 'Select one',
searchHint: 'Search...',
onChanged: (newValue) {
setState(() {
selectedValue = newValue;
});
},
items: dropdownItems.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(item),
)).toList(),
),
],
),
),
);
}
}
代码解释:
-
依赖项:
- 在
pubspec.yaml
文件中添加flutter_searchable_dropdown
依赖项。
- 在
-
数据准备:
- 在
_MyHomePageState
类中,我们创建了一个List<String>
,包含了一些示例数据。
- 在
-
UI构建:
- 使用
Scaffold
构建主页面,包括AppBar
和Padding
。 - 使用
Column
布局来组织文本和下拉列表。 - 使用
SearchableDropdown.single
来创建可搜索的下拉列表。isExpanded: true
:使下拉列表在默认情况下展开。value
:绑定当前选中的值。hint
:当没有选中任何值时显示的提示文本。searchHint
:搜索框中的提示文本。onChanged
:当选中值改变时调用的回调函数。items
:下拉列表中的选项,使用DropdownMenuItem
来创建每个选项。
- 使用
这个示例展示了如何使用flutter_searchable_dropdown
插件来创建一个简单的可搜索下拉列表,并根据用户的选择更新UI。你可以根据需要自定义和扩展这个示例。