繁体   English   中英

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

[英]Flutter search bar with autocomplete

我正在flutter docs中寻找搜索栏,但找不到它,是否有搜索栏小部件在appbar中具有自动完成功能。 例如,我的应用栏上有一个搜索图标。 当按下它时会显示搜索框,当您键入它时,它应该会从带有 listtile 的下拉列表中显示自动完成。 我设法实现了这一点,但它并不容易使用,因为我需要一个下拉列表来显示建议自动完成,然后如果选择了新路线的建议。

这里的搜索操作

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

您可以使用 Stack 来实现自动完成下拉框效果。 下面的示例有 2 个容器 - 都将ListView作为子对象。 一个保存搜索结果,另一个包含一些随机文本作为正文的内容。 ListView (搜索结果)放置在一个 Align Object 内,并且将对齐属性设置为 Alignment.topCenter。 这确保 List 出现在顶部,就在AppBar下方。

更新了评论中提到的帖子(接受的答案)以获得完整的演示。

如上所述:

    @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());
        }
      }

完整演示

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();
    });
  }
}

这个插件对你有帮助, loader_search_bar

Flutter 小部件将搜索字段功能集成到应用栏中,允许接收查询更改回调并自动将新数据集加载到 ListView。 它取代了标准的 AppBar 小部件,需要放置在小部件树中的 Scaffold 元素下方才能正常工作。

在此处输入图片说明

入门 要开始使用 SearchBar,请将其插入 Scaffold 小部件中的 AppBar 元素位置。 无论用例如何,都必须指定 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,
   );
}

可选属性

  • searchHint - 显示提示字符串,直到用户输入任何文本,
  • initialQuery - 在搜索字段中第一次显示的查询值,
  • iconified - 布尔值表示未激活的 SearchBar 的表示方式:如果小部件应在 defaultBar 中显示为操作项,则为 true,如果小部件应与 defaultBar 合并,则为 false(仅默认小部件的前导图标和搜索输入字段显示在此类中案件),
  • autofocus - 布尔值,确定搜索文本字段在可见时是否应获得焦点,
  • 自动激活 - ,
  • attrs - SearchBarAttrs 类实例,允许指定小部件构建期间使用的部分确切值(例如搜索栏颜色、文本大小、边框半径),
  • controller - SearchBarController 对象,它提供了一种与小部件当前状态交互的方式,
  • searchItem - 定义如何在应用栏中构建和定位搜索项小部件,
  • overlayStyle - 激活小部件时应用的状态栏覆盖亮度。

查询回调

要获得有关用户输入的通知,请指定接收当前查询字符串作为参数的 onQueryChanged 和/或 onQuerySubmitted 回调函数:

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

查询集加载器

通过将 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 - 将搜索查询转换为项目列表的函数,然后在 ListView 中呈现(必需),
  • itemBuilder - 为接收到的项目创建 Widget 对象的函数,在 ListView 构建期间为结果集的每个元素调用(必需),
  • loadOnEachChange - 布尔值,指示是否应在每次查询更改时触发 querySetCall; 如果用户提交查询后加载错误查询集,
  • animateChanges - 确定 ListView 的插入和删除操作是否应该被动画化。

搜索项

指定此参数允许自定义搜索项应如何在应用栏中构建和定位。 它可以是动作或菜单小部件。 无论选择这两个中的哪一个,都可以传递两个构造函数参数:

  • builder - 接收当前 BuildContext 并为操作返回 Widget 或为菜单项返回 PopupMenuItem 的函数,
  • 重力 - 可以是 SearchItemGravity 值之一:开始、结束或精确。 如果没有传递参数,SearchBar 将创建默认项目,即具有开始重力的搜索操作图标。

搜索项操作

在此处输入图片说明

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),
  ),
)

搜索项目菜单

在此处输入图片说明

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

另外,请记住,SearchBar 将阻止构建的项目小部件接收点击事件,并将开始搜索操作而不是那样。

希望它会帮助你。

其实很简单。 你可以参考上面的答案了解详情。 让我们按照以下步骤操作:

  • 在自动填充菜单中创建我们想要的项目列表,将其命名为 autoList
  • 再创建一个名为filteredList的emptyList
  • 将 autoList 的所有值添加到 filterList
void initState() {
filteredList.addAll(autoList);
}
  • 创建一个带有 TextField 的自定义搜索栏小部件

  • 我们将获得一个“值”,即从此文本字段输入的文本:例如。 TextFiled(onchange(value){})

  • 假设我们的 autoList 中有字符串,写:

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

完整的 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),
   ),
    ),

现在,沿着搜索栏,我们只需要使用 ListViewBuilder 显示过滤列表。 完毕 :)

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