我正在努力使用自动完成覆盖创建TextField。 我有TextFields的表单,我想根据TextField中输入的内容显示建议。
这样的事情: TextField autocomplete 我不确定小部件的层次结构应该是什么样的,以实现在其他小部件上方显示建议框。我应该使用Stack小部件,OverflowBox小部件还是别的什么?
层次结构示例的任何帮助都表示赞赏。
答案 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模式时,这对我来说效果更好。