Flutter TextField autocomplete overlay

前端 未结 4 1002
梦谈多话
梦谈多话 2021-02-07 15:14

I am struggling to create TextField with autocomplete overlay. I have form with TextFields and I want to display suggestions based on what is typed in TextField.

Someth

相关标签:
4条回答
  • 2021-02-07 15:29

    I implemented for my app using Stack. TextFormField in one container and ListTiles in another container and overlaying the listtile as user types on the container of text input field. You could check out my app.

    The following sample app uses suggestions as user type from api and display in list which user can select by tapping.

    • Screenshot 1
    • Screenshot 2
    • Screenshot 3

    Code Sample:

    import 'package:flutter/material.dart';
    import 'package:search_suggestions/suggestions_page.dart';
    
    void main() => runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Suggestions Demo',
          debugShowCheckedModeBanner: false,
          theme: new ThemeData(
            brightness: Brightness.light,
            primarySwatch: Colors.orange,
          ),
          home: new SuggestionsPage(),
        );
      }
    }
    
    import 'package:http/http.dart' as http;
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'dart:io';
    import 'dart:async';
    
    class SuggestionsPage extends StatefulWidget {
      SuggestionsPage({Key key}) : super(key: key);
      @override
      _SuggestionsPageState createState() => new _SuggestionsPageState();
    }
    
    class _SuggestionsPageState extends State<SuggestionsPage> {
      static const JsonCodec JSON = const JsonCodec();
    
      final key = new GlobalKey<ScaffoldState>();
      final TextEditingController _searchQueryController =
          new TextEditingController();
      final FocusNode _focusNode = new FocusNode();
    
      bool _isSearching = true;
      String _searchText = "";
      List<String> _searchList = List();
      bool _onTap = false;
      int _onTapTextLength = 0;
    
      _SuggestionsPageState() {
        _searchQueryController.addListener(() {
          if (_searchQueryController.text.isEmpty) {
            setState(() {
              _isSearching = false;
              _searchText = "";
              _searchList = List();
            });
          } else {
            setState(() {
              _isSearching = true;
              _searchText = _searchQueryController.text;
              _onTap = _onTapTextLength == _searchText.length;
            });
          }
        });
      }
    
      @override
      void initState() {
        super.initState();
        _isSearching = false;
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          key: key,
          appBar: buildAppbar(context),
          body: buildBody(context),
        );
      }
    
      Widget getFutureWidget() {
        return new FutureBuilder(
            future: _buildSearchList(),
            initialData: List<ListTile>(),
            builder:
                (BuildContext context, AsyncSnapshot<List<ListTile>> childItems) {
              return new Container(
                color: Colors.white,
                height: getChildren(childItems).length * 48.0,
                width: MediaQuery.of(context).size.width,
                child: new ListView(
    //            padding: new EdgeInsets.only(left: 50.0),
                  children: childItems.data.isNotEmpty
                      ? ListTile
                          .divideTiles(
                              context: context, tiles: getChildren(childItems))
                          .toList()
                      : List(),
                ),
              );
            });
      }
    
      List<ListTile> getChildren(AsyncSnapshot<List<ListTile>> childItems) {
        if (_onTap && _searchText.length != _onTapTextLength) _onTap = false;
        List<ListTile> childrenList =
            _isSearching && !_onTap ? childItems.data : List();
        return childrenList;
      }
    
      ListTile _getListTile(String suggestedPhrase) {
        return new ListTile(
          dense: true,
          title: new Text(
            suggestedPhrase,
            style: Theme.of(context).textTheme.body2,
          ),
          onTap: () {
            setState(() {
              _onTap = true;
              _isSearching = false;
              _onTapTextLength = suggestedPhrase.length;
              _searchQueryController.text = suggestedPhrase;
            });
            _searchQueryController.selection = TextSelection
                .fromPosition(new TextPosition(offset: suggestedPhrase.length));
          },
        );
      }
    
      Future<List<ListTile>> _buildSearchList() async {
        if (_searchText.isEmpty) {
          _searchList = List();
          return List();
        } else {
          _searchList = await _getSuggestion(_searchText) ?? List();
    //        ..add(_searchText);
    
          List<ListTile> childItems = new List();
          for (var value in _searchList) {
            if (!(value.contains(" ") && value.split(" ").length > 2)) {
              childItems.add(_getListTile(value));
            }
          }
          return childItems;
        }
      }
    
      Future<List<String>> _getSuggestion(String hintText) async {
        String url = "SOME_TEST_API?s=$hintText&max=4";
    
        var response =
            await http.get(Uri.parse(url), headers: {"Accept": "application/json"});
    
        List decode = JSON.decode(response.body);
        if (response.statusCode != HttpStatus.OK || decode.length == 0) {
          return null;
        }
        List<String> suggestedWords = new List();
    
        if (decode.length == 0) return null;
    
        decode.forEach((f) => suggestedWords.add(f["word"]));
    //    String data = decode[0]["word"];
    
        return suggestedWords;
      }
    
      Widget buildAppbar(BuildContext context) {
        return new AppBar(
          title: new Text('Suggestions Demo'),
        );
      }
    
      Widget buildBody(BuildContext context) {
        return new SafeArea(
          top: false,
          bottom: false,
          child: new SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: new Stack(
              children: <Widget>[
                new Column(
                  children: <Widget>[
                    Container(
                      height: MediaQuery.of(context).size.height,
                      child: new Column(
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: <Widget>[
                          const SizedBox(height: 80.0),
                          new TextFormField(
                            controller: _searchQueryController,
                            focusNode: _focusNode,
                            onFieldSubmitted: (String value) {
                              print("$value submitted");
                              setState(() {
                                _searchQueryController.text = value;
                                _onTap = true;
                              });
                            },
                            onSaved: (String value) => print("$value saved"),
                            decoration: const InputDecoration(
                              border: const UnderlineInputBorder(),
                              filled: true,
                              icon: const Icon(Icons.search),
                              hintText: 'Type two words with space',
                              labelText: 'Seach words *',
                            ),
                          ),
    
                          const SizedBox(height: 40.0),
                          new Center(
                            child: new RaisedButton(
                                color: Colors.orangeAccent,
                                onPressed: () => print("Pressed"),
                                child: const Text(
                                  '    Search    ',
                                  style: const TextStyle(fontSize: 18.0),
                                )),
                          ),
                          const SizedBox(height: 200.0),
                        ],
                      ),
                    ),
                  ],
                ),
                new Container(
                    alignment: Alignment.topCenter,
                    padding: new EdgeInsets.only(
    //                  top: MediaQuery.of(context).size.height * .18,
                        top: 136.0,
                        right: 0.0,
                        left: 38.0),
                    child: _isSearching && (!_onTap) ? getFutureWidget() : null)
              ],
            ),
          ),
        );
      }
    }
    
    0 讨论(0)
  • 2021-02-07 15:32

    You can make use of autocomplete_textfield library to achieve that.

    Basic Usage

      ...
    
                    SimpleAutoCompleteTextField(
                      key: key,
                      suggestions: [
                        "Apple",
                        "Armidillo",
                        "Actual",
                        "Actuary",
                        "America",
                        "Argentina",
                        "Australia",
                        "Antarctica",
                    "Blueberry",],
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.black12,
                        hintText: 'Dictionary'
                      ),
                    ),
    
                    ...  
    

    You can get more examples here.

    Another Option

    You can also make use of flutter_typeahead

    This worked better for me when working with StreamBuilderfor BLoC pattern.

    0 讨论(0)
  • 2021-02-07 15:50

    I have implemented a package, flutter_typeahead to do exactly that. In this package, I use Overlay.of(context).insert, which allows me to insert the suggestions list in the Overlay, making it float on top of all other widgets. I also wrote this article to explain how to do this in detail

    0 讨论(0)
  • 2021-02-07 15:53

    I would probably have a Container with a fixed height, containing a Column with its crossAxisAlignment set to stretch. The first child in the column would be the text field. The second would be an Expanded containing a ListView with a custom delegate to provide the children. Then, as the data in the text field changes, you update the delegate so that the children are updated. Each child would be a ListTile containing an InkWell which, when tapped, fills the text field appropriately.

    0 讨论(0)
提交回复
热议问题