简体   繁体   English

带有自动完成功能的 Flutter 搜索栏

[英]Flutter search bar with autocomplete

I'm looking for a search bar in flutter docs but can't find it, is there a widget for the search bar with autocomplete in appbar.我正在flutter docs中寻找搜索栏,但找不到它,是否有搜索栏小部件在appbar中具有自动完成功能。 For example, I have a search icon on my appbar.例如,我的应用栏上有一个搜索图标。 When one press it show's the search box, when you type it should show autocomplete from the dropdown with listtile.当按下它时会显示搜索框,当您键入它时,它应该会从带有 listtile 的下拉列表中显示自动完成。 I managed to implement this but it's not easy to use because I need a dropdown to show suggestion autocomplete, then use the suggestion for a new route if selected.我设法实现了这一点,但它并不容易使用,因为我需要一个下拉列表来显示建议自动完成,然后如果选择了新路线的建议。

Here the search action 这里的搜索操作

这里是我想要实现的示例图像

You can use Stack to achieve the autocomplete dropdown box effect.您可以使用 Stack 来实现自动完成下拉框效果。 Example below has 2 Containers - both hold ListView as child objects.下面的示例有 2 个容器 - 都将ListView作为子对象。 One holds search results, other has some random text as content for the body.一个保存搜索结果,另一个包含一些随机文本作为正文的内容。 ListView (search result) is placed inside an Align Object and alignment property is set to Alignment.topCenter. ListView (搜索结果)放置在一个 Align Object 内,并且将对齐属性设置为 Alignment.topCenter。 This ensures that List appears at the top, just below the AppBar .这确保 List 出现在顶部,就在AppBar下方。

Updated the Post (accepted answer) mentioned in the comments for a complete a demo.更新了评论中提到的帖子(接受的答案)以获得完整的演示。

As explained above:如上所述:

    @override
      Widget build(BuildContext context) {
        return new Scaffold(
            key: key,
            appBar: buildBar(context),
            body: new Stack(
              children: <Widget>[
                new Container(
                  height: 300.0,
                  padding: EdgeInsets.all(10.0),
                  child: new DefaultTabController(length: 5, child: mainTabView),
                ),
                displaySearchResults(),
              ],
            ));
      }


      Widget displaySearchResults() {
        if (_IsSearching) {
          return new Align(
              alignment: Alignment.topCenter,
              //heightFactor: 0.0,
              child: searchList());
        } else {
          return new Align(alignment: Alignment.topCenter, child: new Container());
        }
      }

Complete demo完整演示

class SearchList extends StatefulWidget {
  SearchList({Key key, this.name}) : super(key: key);

  final String name;

  @override
  _SearchListState createState() => new _SearchListState();
}

class _SearchListState extends State<SearchList> {
  Widget appBarTitle = new Text(
    "",
    style: new TextStyle(color: Colors.white),
  );
  Icon actionIcon = new Icon(
    Icons.search,
    color: Colors.white,
  );
  final key = new GlobalKey<ScaffoldState>();
  final TextEditingController _searchQuery = new TextEditingController();
  List<SearchResult> _list;
  bool _IsSearching;
  String _searchText = "";
  String selectedSearchValue = "";

  _SearchListState() {
    _searchQuery.addListener(() {
      if (_searchQuery.text.isEmpty) {
        setState(() {
          _IsSearching = false;
          _searchText = "";
        });
      } else {
        setState(() {
          _IsSearching = true;
          _searchText = _searchQuery.text;
        });
      }
    });
  }

  @override
  void initState() {
    super.initState();
    _IsSearching = false;
    createSearchResultList();
  }

  void createSearchResultList() {
    _list = <SearchResult>[
      new SearchResult(name: 'Google'),
      new SearchResult(name: 'IOS'),
      new SearchResult(name: 'IOS2'),
      new SearchResult(name: 'Android'),
      new SearchResult(name: 'Dart'),
      new SearchResult(name: 'Flutter'),
      new SearchResult(name: 'Python'),
      new SearchResult(name: 'React'),
      new SearchResult(name: 'Xamarin'),
      new SearchResult(name: 'Kotlin'),
      new SearchResult(name: 'Java'),
      new SearchResult(name: 'RxAndroid'),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        key: key,
        appBar: buildBar(context),
        body: new Stack(
          children: <Widget>[
            new Container(
              height: 300.0,
              padding: EdgeInsets.all(10.0),
              child: new Container(
                child: ListView(
                  children: <Widget>[
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                    new Text("Hello World!"),
                  ],
                ),
              ),
            ),
            displaySearchResults(),
          ],
        ));
  }

  Widget displaySearchResults() {
    if (_IsSearching) {
      return new Align(
          alignment: Alignment.topCenter,
          child: searchList());
    } else {
      return new Align(alignment: Alignment.topCenter, child: new Container());
    }
  }

  ListView searchList() {
    List<SearchResult> results = _buildSearchList();
    return ListView.builder(
      itemCount: _buildSearchList().isEmpty == null ? 0 : results.length,
      itemBuilder: (context, int index) {
        return Container(
          decoration: new BoxDecoration(
              color: Colors.grey[100],
            border: new Border(
              bottom: new BorderSide(
                  color: Colors.grey,
                width: 0.5
              )
          )
          ),

          child: ListTile(
            onTap: (){},
            title: Text(results.elementAt(index).name,
                style: new TextStyle(fontSize: 18.0)),
          ),
        );
      },
    );
  }

  List<SearchResult> _buildList() {
    return _list.map((result) => new SearchResult(name: result.name)).toList();
  }

  List<SearchResult> _buildSearchList() {
    if (_searchText.isEmpty) {
      return _list.map((result) => new SearchResult(name: result.name)).toList();
    } else {
      List<SearchResult> _searchList = List();
      for (int i = 0; i < _list.length; i++) {
        SearchResult result = _list.elementAt(i);
        if ((result.name).toLowerCase().contains(_searchText.toLowerCase())) {
          _searchList.add(result);
        }
      }
      return _searchList
          .map((result) => new SearchResult(name: result.name))
          .toList();
    }
  }

  Widget buildBar(BuildContext context) {
    return new AppBar(
      centerTitle: true,
      title: appBarTitle,
      actions: <Widget>[
        new IconButton(
          icon: actionIcon,
          onPressed: () {
            _displayTextField();
          },
        ),

        // new IconButton(icon: new Icon(Icons.more), onPressed: _IsSearching ? _showDialog(context, _buildSearchList()) : _showDialog(context,_buildList()))
      ],
    );
  }

  String selectedPopupRoute = "My Home";
  final List<String> popupRoutes = <String>[
    "My Home",
    "Favorite Room 1",
    "Favorite Room 2"
  ];

  void _displayTextField() {
    setState(() {
      if (this.actionIcon.icon == Icons.search) {
        this.actionIcon = new Icon(
          Icons.close,
          color: Colors.white,
        );
        this.appBarTitle = new TextField(
          autofocus: true,
          controller: _searchQuery,
          style: new TextStyle(
            color: Colors.white,
          ),
        );

        _handleSearchStart();
      } else {
        _handleSearchEnd();
      }
    });
  }

  void _handleSearchStart() {
    setState(() {
      _IsSearching = true;
    });
  }

  void _handleSearchEnd() {
    setState(() {
      this.actionIcon = new Icon(
        Icons.search,
        color: Colors.white,
      );
      this.appBarTitle = new Text(
        "",
        style: new TextStyle(color: Colors.white),
      );
      _IsSearching = false;
      _searchQuery.clear();
    });
  }
}

this plugin will helpful for you, loader_search_bar这个插件对你有帮助, loader_search_bar

Flutter widget integrating search field feature into app bar, allowing to receive query change callbacks and automatically load new data set into ListView. Flutter 小部件将搜索字段功能集成到应用栏中,允许接收查询更改回调并自动将新数据集加载到 ListView。 It replaces standard AppBar widget and needs to be placed underneath Scaffold element in the widget tree to work properly.它取代了标准的 AppBar 小部件,需要放置在小部件树中的 Scaffold 元素下方才能正常工作。

在此处输入图片说明

Getting started To start using SearchBar insert it in place of an AppBar element in the Scaffold widget.入门 要开始使用 SearchBar,请将其插入 Scaffold 小部件中的 AppBar 元素位置。 Regardless of the use case, defaultBar named argument has to be specified, which basically is a widget that will be displayed whenever SearchBar is not in activated state:无论用例如何,都必须指定 defaultBar 命名参数,它基本上是一个小部件,只要 SearchBar 未处于激活状态就会显示:

@override
Widget build(BuildContext context) {
   return Scaffold(
 appBar: SearchBar(
   defaultBar: AppBar(
     leading: IconButton(
       icon: Icon(Icons.menu),
       onPressed: _openDrawer,
     ),
     title: Text('Default app bar title'),
   ),
   ...
 ),
 body: _body,
 drawer: _drawer,
   );
}

Optional attributes可选属性

  • searchHint - hint string being displayed until user inputs any text, searchHint - 显示提示字符串,直到用户输入任何文本,
  • initialQuery - query value displayed for the first time in search field, initialQuery - 在搜索字段中第一次显示的查询值,
  • iconified - boolean value indicating way of representing non-activated SearchBar: true if widget should be showed as an action item in defaultBar, false if widget should be merged with defaultBar (only leading icon of the default widget and search input field are displayed in such case), iconified - 布尔值表示未激活的 SearchBar 的表示方式:如果小部件应在 defaultBar 中显示为操作项,则为 true,如果小部件应与 defaultBar 合并,则为 false(仅默认小部件的前导图标和搜索输入字段显示在此类中案件),
  • autofocus - boolean value determining if search text field should get focus whenever it becomes visible, autofocus - 布尔值,确定搜索文本字段在可见时是否应获得焦点,
  • autoActive - ,自动激活 - ,
  • attrs - SearchBarAttrs class instance allowing to specify part of exact values used during widget building (eg search bar colors, text size, border radius), attrs - SearchBarAttrs 类实例,允许指定小部件构建期间使用的部分确切值(例如搜索栏颜色、文本大小、边框半径),
  • controller - SearchBarController object that provides a way of interacing with current state of the widget, controller - SearchBarController 对象,它提供了一种与小部件当前状态交互的方式,
  • searchItem - defining how to build and position search item widget in app bar, searchItem - 定义如何在应用栏中构建和定位搜索项小部件,
  • overlayStyle - status bar overlay brightness applied when widget is activated. overlayStyle - 激活小部件时应用的状态栏覆盖亮度。

Query callbacks查询回调

To get notified about user input specify onQueryChanged and/or onQuerySubmitted callback functions that receive current query string as an argument:要获得有关用户输入的通知,请指定接收当前查询字符串作为参数的 onQueryChanged 和/或 onQuerySubmitted 回调函数:

appBar: SearchBar(
   ...
   onQueryChanged: (query) => _handleQueryChanged(context, query),
   onQuerySubmitted: (query) => _handleQuerySubmitted(context, query),
),

QuerySetLoader查询集加载器

By passing QuerySetLoader object as an argument one can additionally benefit from search results being automatically built as ListView widget whenever search query changes:通过将 QuerySetLoader 对象作为参数传递,当搜索查询更改时,您还可以从搜索结果自动构建为 ListView 小部件中受益:

appBar: SearchBar(
  ...
   loader: QuerySetLoader<Item>(
   querySetCall: _getItemListForQuery,
   itemBuilder: _buildItemWidget,
   loadOnEachChange: true,
   animateChanges: true,
  ),
),

List<Item> _getItemListForQuery(String query) { ... }

Widget _buildItemWidget(Item item) { ... }
  • querySetCall - function transforming search query into list of items being then rendered in ListView (required), querySetCall - 将搜索查询转换为项目列表的函数,然后在 ListView 中呈现(必需),
  • itemBuilder - function creating Widget object for received item, called during ListView building for each element of the results set (required), itemBuilder - 为接收到的项目创建 Widget 对象的函数,在 ListView 构建期间为结果集的每个元素调用(必需),
  • loadOnEachChange - boolean value indicating whether querySetCall should be triggered on each query change; loadOnEachChange - 布尔值,指示是否应在每次查询更改时触发 querySetCall; if false query set is loaded once user submits query,如果用户提交查询后加载错误查询集,
  • animateChanges - determines whether ListView's insert and remove operations should be animated. animateChanges - 确定 ListView 的插入和删除操作是否应该被动画化。

SearchItem搜索项

Specifying this parameter allows to customize how search item should be built and positioned in app bar.指定此参数允许自定义搜索项应如何在应用栏中构建和定位。 It can be either action or menu widget.它可以是动作或菜单小部件。 No matter which of these two is picked, two constructor arguments can be passed:无论选择这两个中的哪一个,都可以传递两个构造函数参数:

  • builder - function receiving current BuildContext and returning Widget for action or PopupMenuItem for menu item, builder - 接收当前 BuildContext 并为操作返回 Widget 或为菜单项返回 PopupMenuItem 的函数,
  • gravity - can be one of SearchItemGravity values: start, end or exactly.重力 - 可以是 SearchItemGravity 值之一:开始、结束或精确。 If no arguments are passed, SearchBar will create default item which is search action icon with start gravity.如果没有传递参数,SearchBar 将创建默认项目,即具有开始重力的搜索操作图标。

SearchItem.action搜索项操作

在此处输入图片说明

appBar: SearchBar(
  // ...
  searchItem: SearchItem.action(
    builder: (_) => Padding(
      padding: EdgeInsets.all(12.0),
      child: Icon(
        Icons.find_in_page,
        color: Colors.indigoAccent,
      ),
    ),
    gravity: SearchItemGravity.exactly(1),
  ),
)

SearchItem.menu搜索项目菜单

在此处输入图片说明

appBar: SearchBar(
  // ...
  searchItem: SearchItem.menu(
    builder: (_) => PopupMenuItem(
      child: Text("Search  🔍"),
      value: "search",
    ),
    gravity: SearchItemGravity.end,
  ),
)

Also, bear in mind that SearchBar will prevent built item widget from receiving tap events and will begin search action rather than that.另外,请记住,SearchBar 将阻止构建的项目小部件接收点击事件,并将开始搜索操作而不是那样。

hope it will help you.希望它会帮助你。

It is actually very simple.其实很简单。 you can refer above answers for details.你可以参考上面的答案了解详情。 Let's follow these steps:让我们按照以下步骤操作:

  • Create a list of items we want to have in the autofill menu, lets name it autoList在自动填充菜单中创建我们想要的项目列表,将其命名为 autoList
  • Create one more emptyList named filteredList再创建一个名为filteredList的emptyList
  • Add all the values of autoList to filterList将 autoList 的所有值添加到 filterList
void initState() {
filteredList.addAll(autoList);
}
  • Create a custom search bar widget with a TextField in it创建一个带有 TextField 的自定义搜索栏小部件

  • we will be getting a 'value' ie the text entered from this Textfield: eg.我们将获得一个“值”,即从此文本字段输入的文本:例如。 TextFiled(onchange(value){}) TextFiled(onchange(value){})

  • Assuming that we have strings in our autoList, write:假设我们的 autoList 中有字符串,写:

filteredList.removeWhere((i) => i.contains(value.toString())==false); 

The complete TextField widget will look like:完整的 TextField 小部件将如下所示:

TextField(
     onChanged: (value) {
     setState(() {
     filteredList.clear(); //for the next time that we search we want the list to be unfilterted                            
     filteredList.addAll(autoList); //getting list to original state

//removing items that do not contain the entered Text                                                            
     filteredList.removeWhere((i) => i.contains(value.toString())==false); 

//following is just a bool parameter to keep track of lists
     searched=!searched;
     });


    },
  controller: editingController,
  decoration: InputDecoration(
  border: InputBorder.none,
  labelText: "Search for the filtered list",
  prefixIcon: Icon(Icons.search),
   ),
    ),

Now, along the search bar, we just have to display filteredList with ListViewBuilder.现在,沿着搜索栏,我们只需要使用 ListViewBuilder 显示过滤列表。 done :)完毕 :)

import 'package:flutter/material.dart';

class SearchText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Searchable Text"),
        actions: <Widget>[
          IconButton(
              icon: Icon(Icons.search),
              onPressed: () {
                showSearch(
                  context: context,
                  delegate: DataSearch(),
                );
              })
        ],
      ),
      drawer: Drawer(),
    );
  }
}

class DataSearch extends SearchDelegate<String> {
  final cities = ['Ankara', 'İzmir', 'İstanbul', 'Samsun', 'Sakarya'];
  var recentCities = ['Ankara'];

  @override
  List<Widget> buildActions(BuildContext context) {
    return [
      IconButton(
          icon: Icon(Icons.clear),
          onPressed: () {
            query = "";
          })
    ];
  }

  @override
  Widget buildLeading(BuildContext context) {
    return IconButton(
        icon: AnimatedIcon(
          icon: AnimatedIcons.menu_arrow,
          progress: transitionAnimation,
        ),
        onPressed: () {
          close(context, null);
        });
  }

  @override
  Widget buildResults(BuildContext context) {
    return Center(
      child: Container(
        width: 100,
        height: 100,
        child: Card(
          color: Colors.red,
          child: Center(child: Text(query)),
        ),
      ),
    );
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    final suggestionList = query.isEmpty
        ? recentCities
        : cities.where((p) => p.startsWith(query)).toList();

    return ListView.builder(
      itemBuilder: (context, index) => ListTile(
        onTap: () {
          showResults(context);
        },
        leading: Icon(Icons.location_city),
        title: RichText(
          text: TextSpan(
            text: suggestionList[index].substring(0, query.length),
            style: TextStyle(
              color: Colors.black,
              fontWeight: FontWeight.bold,
            ),
            children: [
              TextSpan(
                text: suggestionList[index].substring(query.length),
              ),
            ],
          ),
        ),
      ),
      itemCount: suggestionList.length,
    );
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM