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

时间:2018-04-26 20:11:31

标签: flutter

我在flutter docs中寻找搜索栏但却找不到它,是否有搜索栏的小部件,在appbar中自动完成。例如,我在appbar的顶部有图标搜索。当按下它显示搜索框时,开始输入时显示自动完成,就像下拉列表一样。我设法找到了这个但不容易使用,因为我需要下拉显示建议自动完成,然后使用建议选择新路由。

Here the search action

here the example image what i want to achieve

4 个答案:

答案 0 :(得分:1)

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

更新了注释中提到的Post(可接受的答案)以完成演示。

如上所述:

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

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

答案 1 :(得分:0)

此插件将为您提供帮助,loader_search_bar

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

enter image description here

开始 要开始使用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-布尔值,用于确定搜索文本字段在可见时是否应获得焦点
  • autoActive-,
  • attrs-SearchBarAttrs类实例,可以指定在小部件构建期间使用的部分确切值(例如,搜索栏颜色,文本大小,边框半径),
  • controller-SearchBarController对象,提供一种与小部件当前状态交互的方式,
  • searchItem-定义如何在应用栏中构建和放置搜索项小部件,
  • overlayStyle-激活小部件时应用的状态栏覆盖亮度。

查询回调

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

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

QuerySetLoader

通过将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的插入和删除操作的动画。

SearchItem

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

  • builder-函数,该函数接收当前的BuildContext并返回Widget以进行操作,或者返回PopupMenuItem作为菜单项,
  • gravity-可以是SearchItemGravity值之一:开始,结束或正好。如果未传递任何参数,SearchBar将创建默认项,即具有开始重力的搜索动作图标。

SearchItem.action

enter image description here

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

enter image description here

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

此外,请记住,SearchBar会阻止内置项目小部件接收点击事件,而将开始搜索动作。

希望它会对您有所帮助。

答案 2 :(得分:0)

实际上非常简单。您可以参考上面的答案以获取详细信息。让我们按照以下步骤操作:

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

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

  • 假设我们的自动列表中有字符串,请输入:

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显示filteredList。完成:)

答案 3 :(得分:0)

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