搜索插件、代码仓与 APP 分类

插件详情v1.1.3

searchable_dropdown

一个控件,允许用户在对话框或菜单中以下拉列表形式搜索通过自定义键盘输入的关键词字符串,支持单选或多选。

近 30 天下载
384
喜欢数
301
Pub 分数
40

安装插件

flutter pub add searchable_dropdown

插件介绍

基于插件公开信息、社区数据与项目使用情况整理的选型参考。

一个 Flutter 小部件,允许用户使用可自定义的键盘在选项列表中进行搜索。它支持单选和多选模式,并以下拉菜单或对话框的形式呈现选择项。非常适合需要从大量列表中快速查找并选择内容的表单和数据录入场景。该小部件高度可配置,可自定义输入字段、下拉菜单外观和搜索行为。通过减少在长列表中的导航时间,提升用户体验。

适合场景

  • 具有大量选项列表的表单输入
  • 用户资料选择与筛选
  • 电子商务商品分类选择
  • 管理面板中的动态数据过滤
  • 带搜索功能的多选下拉菜单
  • 可自定义的搜索界面

主要特点

  • 支持实时过滤的可搜索下拉菜单
  • 支持单选和多选
  • 可自定义的键盘和输入字段
  • 在对话框或菜单中显示的下拉列表
  • 可扩展的样式和主题支持
  • 高效处理大型数据集

适合对象

  • 开发具有长选项列表的表单的开发者
  • 需要快速数据查找的应用程序
  • 需要直观选择的UI密集型应用程序
  • 希望拥有可重复使用、可配置下拉组件的团队
  • 强调数据输入用户体验的项目

使用建议

  • 有限的平台特定集成(未列出平台)
  • 可能需要额外的样式才能适应复杂主题
  • 性能取决于列表大小和过滤逻辑
  • 无内置本地化支持
  • 未针对无分页的大数据集进行优化

原始 README

以下为关联源码仓库的 README 原文快照,最新内容请访问源码仓库。

searchable_dropdown

Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list presented as a dropdown in a dialog box or a menu.

Platforms

This widget has been successfully tested on iOS, Android and Chrome.

Examples

The following examples are extracted from the example project available in the repository.

Gallery

See code below.

Example name Demonstration
Single dialog Single dialog
Multi dialog Multi dialog
Single done button
dialog
Single done button dialog
Multi custom display
dialog
Multi custom display dialog
Multi select 3 dialog Multi select 3 dialog
Single menu Single menu
Multi menu Multi menu
Multi menu select
all/none
Multi menu select all or none
Multi dialog select
all/none without clear
Multi dialog select all or none without clear
Single dialog custom
keyboard
Single dialog custom keyboard
Single dialog object Single dialog object
Single dialog overflow Single dialog overflow
Single dialog readOnly Single dialog readOnly
Single dialog disabled Single dialog disabled
Code
Plugin usage

Add to your pubspec.yaml in the dependencies section:

  searchable_dropdown:

Get packages with command:

flutter packages get

Import:

import 'package:searchable_dropdown/searchable_dropdown.dart';

Call either the single choice or the multiple choice constructor.

Single choice constructor

Search choices Widget with a single choice that opens a dialog or a menu to let the user do the selection conveniently with a search.

factory SearchableDropdown.single({
    Key key,
    @required List<DropdownMenuItem<T>> items,
    @required Function onChanged,
    T value,
    TextStyle style,
    dynamic searchHint,
    dynamic hint,
    dynamic disabledHint,
    dynamic icon = const Icon(Icons.arrow_drop_down),
    dynamic underline,
    dynamic doneButton,
    dynamic label,
    dynamic closeButton = "Close",
    bool displayClearIcon = true,
    Icon clearIcon = const Icon(Icons.clear),
    Color iconEnabledColor,
    Color iconDisabledColor,
    double iconSize = 24.0,
    bool isExpanded = false,
    bool isCaseSensitiveSearch = false,
    Function searchFn,
    Function onClear,
    Function selectedValueWidgetFn,
    TextInputType keyboardType = TextInputType.text,
    Function validator,
    bool assertUniqueValue = true,
    Function displayItem,
    bool dialogBox = true,
    BoxConstraints menuConstraints,
    bool readOnly: false,
    Color menuBackgroundColor,
}
)
  • items with child: Widget displayed ; value: any object with .toString() used to match search keyword.
  • onChanged Function with parameter: value not returning executed after the selection is done.
  • value value to be preselected.
  • style used for the hint if it is given is String.
  • searchHint String|Widget|Function with no parameter returning String|Widget displayed at the top of the search dialog box.
  • hint String|Widget|Function with no parameter returning String|Widget displayed before any value is selected or after the selection is cleared.
  • disabledHint String|Widget|Function with no parameter returning String|Widget displayed instead of hint when the widget is displayed.
  • icon String|Widget|Function with parameter: value returning String|Widget displayed next to the selected item or the hint if none.
  • underline String|Widget|Function with parameter: value returning String|Widget displayed below the selected item or the hint if none.
  • doneButton String|Widget|Function with parameter: value returning String|Widget displayed at the top of the search dialog box.
  • label String|Widget|Function with parameter: value returning String|Widget displayed above the selected item or the hint if none.
  • closeButton String|Widget|Function with parameter: value returning String|Widget displayed at the bottom of the search dialog box.
  • displayClearIcon whether or not to display an icon to clear the selected value.
  • clearIcon Icon to be used for clearing the selected value.
  • iconEnabledColor Color to be used for enabled icons.
  • iconDisabledColor Color to be used for disabled icons.
  • iconSize for the icons next to the selected value (icon and clearIcon).
  • isExpanded can be necessary to avoid pixel overflows (zebra symptom).
  • isCaseSensitiveSearch only used when searchFn is not specified.
  • searchFn Function with parameters: keyword, items returning List as the list of indexes for the items to be displayed.
  • onClear Function with no parameter not returning executed when the clear icon is tapped.
  • selectedValueWidgetFn Function with parameter: item returning Widget to be used to display the selected value.
  • keyboardType used for the search.
  • validator Function with parameter: value returning String displayed below selected value when not valid and null when valid.
  • assertUniqueValue whether to run a consistency check of the list of items.
  • displayItem Function with parameters: item, selected returning Widget to be displayed in the search list.
  • dialogBox whether the search should be displayed as a dialog box or as a menu below the selected value if any.
  • menuConstraints BoxConstraints used to define the zone where to display the search menu. Example: BoxConstraints.tight(Size.fromHeight(250)) . Not to be used for dialogBox = true.
  • readOnly bool whether to let the user choose the value to select or just present the selected value if any.
  • menuBackgroundColor Color background color of the menu whether in dialog box or menu mode.
Multiple choice constructor

Search choices Widget with a multiple choice that opens a dialog or a menu to let the user do the selection conveniently with a search.

SearchableDropdown<T>.multiple(
{
    Key key,
    @required List<DropdownMenuItem<T>> items,
    @required Function onChanged,
    List<int> selectedItems: const [],
    TextStyle style,
    dynamic searchHint,
    dynamic hint,
    dynamic disabledHint,
    dynamic icon: const Icon(Icons.arrow_drop_down),
    dynamic underline,
    dynamic doneButton: "Done",
    dynamic label,
    dynamic closeButton: "Close",
    bool displayClearIcon: true,
    Icon clearIcon: const Icon(Icons.clear),
    Color iconEnabledColor,
    Color iconDisabledColor,
    double iconSize: 24.0,
    bool isExpanded: false,
    bool isCaseSensitiveSearch: false,
    Function searchFn,
    Function onClear,
    Function selectedValueWidgetFn,
    TextInputType keyboardType: TextInputType.text,
    Function validator,
    Function displayItem,
    bool dialogBox: true,
    BoxConstraints menuConstraints,
    bool readOnly: false,
    Color menuBackgroundColor,
}
)
  • items with child: Widget displayed ; value: any object with .toString() used to match search keyword.
  • onChanged Function with parameter: selectedItems not returning executed after the selection is done.
  • selectedItems indexes of items to be preselected.
  • style used for the hint if it is given is String.
  • searchHint String|Widget|Function with no parameter returning String|Widget displayed at the top of the search dialog box.
  • hint String|Widget|Function with no parameter returning String|Widget displayed before any value is selected or after the selection is cleared.
  • disabledHint String|Widget|Function with no parameter returning String|Widget displayed instead of hint when the widget is displayed.
  • icon String|Widget|Function with parameter: selectedItems returning String|Widget displayed next to the selected items or the hint if none.
  • underline String|Widget|Function with parameter: selectedItems returning String|Widget displayed below the selected items or the hint if none.
  • doneButton String|Widget|Function with parameter: selectedItems returning String|Widget displayed at the top of the search dialog box. Cannot be null in multiple selection mode.
  • label String|Widget|Function with parameter: selectedItems returning String|Widget displayed above the selected items or the hint if none.
  • closeButton String|Widget|Function with parameter: selectedItems returning String|Widget displayed at the bottom of the search dialog box.
  • displayClearIcon whether or not to display an icon to clear the selected values.
  • clearIcon Icon to be used for clearing the selected values.
  • iconEnabledColor Color to be used for enabled icons.
  • iconDisabledColor Color to be used for disabled icons.
  • iconSize for the icons next to the selected values (icon and clearIcon).
  • isExpanded can be necessary to avoid pixel overflows (zebra symptom).
  • isCaseSensitiveSearch only used when searchFn is not specified.
  • searchFn Function with parameters: keyword, items returning List as the list of indexes for the items to be displayed.
  • onClear Function with no parameter not returning executed when the clear icon is tapped.
  • selectedValueWidgetFn Function with parameter: item returning Widget to be used to display the selected values.
  • keyboardType used for the search.
  • validator Function with parameter: selectedItems returning String displayed below selected values when not valid and null when valid.
  • displayItem Function with parameters: item, selected returning Widget to be displayed in the search list.
  • dialogBox whether the search should be displayed as a dialog box or as a menu below the selected values if any.
  • menuConstraints BoxConstraints used to define the zone where to display the search menu. Example: BoxConstraints.tight(Size.fromHeight(250)) . Not to be used for dialogBox = true.
  • readOnly bool whether to let the user choose the value to select or just present the selected value if any.
  • menuBackgroundColor Color background color of the menu whether in dialog box or menu mode.
Example app usage

Clone repository:

git clone https://github.com/icemanbsi/searchable_dropdown.git

Go to plugin folder:

cd searchable_dropdown

Optionally enable web:

flutter config --enable-web

Create project:

flutter create .

To run automated tests:

flutter test

Optionally generate documentation:

pub global activate dartdoc
dartdoc

Go to example app folder:

cd example

To run web:

run -d chrome

To build web to folder build/web:

flutter build web

To run on a connected device:

flutter run

To build Android app to build/app/outputs/apk/release/app-release.apk:

flutter build apk

To build iOS app on Mac:

flutter build ios
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: 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,
      ),
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
                ? Icon(
                    Icons.radio_button_checked,
                    color: Colors.grey,
                  )
                : Icon(
                    Icons.radio_button_unchecked,
                    color: Colors.grey,
                  ),
            SizedBox(width: 7),
            Expanded(
              child: item,
            ),
          ]));
        },
        isExpanded: true,
      ),
Multi custom display dialog
      SearchableDropdown.multiple(
        items: items,
        selectedItems: selectedItems,
        hint: Padding(
          padding: const EdgeInsets.all(12.0),
          child: Text("Select any"),
        ),
        searchHint: "Select any",
        onChanged: (value) {
          setState(() {
            selectedItems = value;
          });
        },
        displayItem: (item, selected) {
          return (Row(children: [
            selected
                ? Icon(
                    Icons.check,
                    color: Colors.green,
                  )
                : Icon(
                    Icons.check_box_outline_blank,
                    color: Colors.grey,
                  ),
            SizedBox(width: 7),
            Expanded(
              child: item,
            ),
          ]));
        },
        selectedValueWidgetFn: (item) {
          return (Center(
              child: Card(
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10),
                    side: BorderSide(
                      color: Colors.brown,
                      width: 0.5,
                    ),
                  ),
                  margin: EdgeInsets.all(12),
                  child: Padding(
                    padding: const EdgeInsets.all(8),
                    child: Text(item.toString()),
                  ))));
        },
        doneButton: (selectedItemsDone, doneContext) {
          return (RaisedButton(
              onPressed: () {
                Navigator.pop(doneContext);
                setState(() {});
              },
              child: Text("Save")));
        },
        closeButton: null,
        style: TextStyle(fontStyle: FontStyle.italic),
        searchFn: (String keyword, items) {
          List<int> ret = List<int>();
          if (keyword != null && 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: Icon(Icons.clear_all),
        icon: Icon(Icons.arrow_drop_down_circle),
        label: "Label for multi",
        underline: Container(
          height: 1.0,
          decoration: 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 (RaisedButton(
              onPressed: selectedItemsDone.length != 3
                  ? null
                  : () {
                      Navigator.pop(doneContext);
                      setState(() {});
                    },
              child: 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(Size.fromHeight(350)),
      ),
Multi menu
      SearchableDropdown.multiple(
        items: items,
        selectedItems: selectedItems,
        hint: "Select any",
        searchHint: "",
        doneButton: "Close",
        closeButton: SizedBox.shrink(),
        onChanged: (value) {
          setState(() {
            selectedItems = value;
          });
        },
        dialogBox: false,
        isExpanded: true,
        menuConstraints: BoxConstraints.tight(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>[
              RaisedButton(
                  onPressed: () {
                    setState(() {
                      selectedItems.clear();
                      selectedItems.addAll(
                          Iterable<int>.generate(items.length).toList());
                    });
                  },
                  child: Text("Select all")),
              RaisedButton(
                  onPressed: () {
                    setState(() {
                      selectedItems.clear();
                    });
                  },
                  child: Text("Select none")),
            ],
          );
        },
        isExpanded: true,
        menuConstraints: BoxConstraints.tight(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>[
              RaisedButton(
                  onPressed: () {
                    setState(() {
                      selectedItems.clear();
                      selectedItems.addAll(
                          Iterable<int>.generate(items.length).toList());
                    });
                  },
                  child: Text("Select all")),
              RaisedButton(
                  onPressed: () {
                    setState(() {
                      selectedItems.clear();
                    });
                  },
                  child: Text("Select none")),
            ],
          );
        },
        isExpanded: true,
      ),
Single dialog custom keyboard
      SearchableDropdown.single(
        items: Iterable<int>.generate(20).toList().map((i) {
          return (DropdownMenuItem(
            child: Text(i.toString()),
            value: 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(
              child: Text(exNum.numberString), value: exNum));
        }).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: [
          DropdownMenuItem(
            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:
                "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: [
          DropdownMenuItem(
            child: Text(
                "one item"),
            value:
            "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: [
          DropdownMenuItem(
            child: Text(
                "one item"),
            value:
            "one item",
          )
        ],
        value: "one item",
        hint: "Select one",
        searchHint: "Select one",
        disabledHint: "Disabled",
        onChanged: null,
        dialogBox: true,
        isExpanded: true,
      ),

Feature requests/comments/questions/bugs

Feel free to log your feature requests/comments/questions/bugs here: https://github.com/icemanbsi/searchable_dropdown/issues

Contributions

We would be happy to merge pull request proposals provided that:

  • they don't break the compilation
  • they pass the automated testing
  • they provide the relevant adaptations to documentation and automated testing
  • they bring value
  • they don't completely transform the code
  • they are readable (though, I enjoy https://www.ioccc.org/ as a contest full of curiosities)

Contributions and forks are very welcome!

In your pull request, feel free to add your line in the contributors section below:

Contributors
  • https://github.com/icemanbsi
  • https://github.com/lcuis

CI/CD

Continuous integration/deployment status: CI-CD

所属分类

源码仓库

使用此插件的项目

暂无相关数据。