Flutter TextField自动完成覆盖

时间:2017-04-27 08:25:40

标签: dart flutter

我正在努力使用自动完成覆盖创建TextField。 我有TextFields的表单,我想根据TextField中输入的内容显示建议。

这样的事情: TextField autocomplete 我不确定小部件的层次结构应该是什么样的,以实现在其他小部件上方显示建议框。我应该使用Stack小部件,OverflowBox小部件还是别的什么?

层次结构示例的任何帮助都表示赞赏。

4 个答案:

答案 0 :(得分:7)

我使用Stack为我的应用程序实现了。 TextFormField在一个容器中,ListTiles在另一个容器中,并将listtile作为用户类型覆盖在文本输入字段的容器上。你可以看看  my app

以下示例应用程序使用建议作为api中的用户类型,并在列表中显示哪个用户可以通过点击进行选择。

代码示例:

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

答案 1 :(得分:4)

我已经实现了一个软件包flutter_typeahead来做到这一点。在此程序包中,我使用Overlay.of(context).insert,它使我可以在Overlay中插入建议列表,使其浮于所有其他小部件之上。我还写了this article来详细解释如何做

答案 2 :(得分:3)

我可能有一个具有固定高度的Container,其中包含一个其crossAxisAlignment设置为stretch的Column。列中的第一个子节点是文本字段。第二个是Expanded,包含一个带有自定义委托的ListView来提供子节点。然后,随着文本字段中的数据发生更改,您将更新委托以便更新子项。每个子节点都是一个包含InkWell的ListTile,当点击时,它会适当地填充文本字段。

答案 3 :(得分:2)

您可以使用autocomplete_textfield库来实现。

基本用法

  ...

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

                ...  

您可以获得更多示例here

另一个选项

您还可以使用flutter_typeahead

在使用StreamBuilder的BLoC模式时,这对我来说效果更好。