19 votes

Superposition autocomplète d'un champ de texte Flutter

J'ai du mal à créer un TextField avec une superposition d'autocomplétion. J'ai un formulaire avec des TextFields et je veux afficher des suggestions en fonction de ce qui est tapé dans le TextField.

Quelque chose comme ça : Autocomplétion des champs de texte Je ne sais pas exactement à quoi doit ressembler la hiérarchie des widgets pour que la boîte à suggestions s'affiche au-dessus des autres widgets. Dois-je utiliser le widget Stack, le widget OverflowBox ou autre chose ?

Toute aide par exemple hiérarchique est appréciée.

26voto

J'ai mis en place un paquet, flutter_typeahead pour faire exactement ça. Dans ce paquet, j'utilise Overlay.of(context).insert qui me permet d'insérer la liste de suggestions dans la superposition, en la faisant flotter au-dessus de tous les autres widgets. J'ai également écrit cet article pour expliquer en détail comment procéder

16voto

Ronin Points 71

J'ai implémenté mon application en utilisant Stack. TextFormField dans un conteneur et ListTiles dans un autre conteneur et en superposant la liste des tuiles lorsque l'utilisateur tape sur le conteneur du champ de saisie de texte. Vous pouvez consulter mon application .

L'exemple d'application suivant utilise les suggestions de l'api en fonction du type d'utilisateur et les affiche dans une liste que l'utilisateur peut sélectionner en tapant dessus.

Exemple de code :

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

13voto

nonybrighto Points 1157

Vous pouvez utiliser Champ de texte autocomplété bibliothèque pour y parvenir.

Utilisation de base

  ...

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

                ...  

Vous pouvez obtenir d'autres exemples aquí .

Une autre option

Vous pouvez également utiliser flutter_typeahead

Cela a mieux fonctionné pour moi lorsque j'ai travaillé avec StreamBuilder pour le modèle BLoC.

7voto

Ian Hickson Points 104

J'aurais probablement un conteneur d'une hauteur fixe, contenant une colonne dont l'alignement transversal est réglé sur l'étirement. Le premier enfant de la colonne serait le champ de texte. Le deuxième serait un Expanded contenant un ListView avec un délégué personnalisé pour fournir les enfants. Ensuite, lorsque les données du champ de texte changent, vous mettez à jour le délégué pour que les enfants soient mis à jour. Chaque enfant serait une ListTile contenant un InkWell qui, lorsqu'on y touche, remplit le champ de texte de manière appropriée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X