Autocomplete 自动填充,在我们输入内容的时候进行关键字联想。效果在输入框下方展示。其属于Flutter原生自带的组件。
Autocomplete组件
class Autocomplete<T extends Object> extends StatelessWidget {
/// Creates an instance of [Autocomplete].
const Autocomplete({
super.key,
required this.optionsBuilder,
this.displayStringForOption = RawAutocomplete.defaultStringForOption,
this.fieldViewBuilder = _defaultFieldViewBuilder,
this.onSelected,
this.optionsMaxHeight = 200.0,
this.optionsViewBuilder,
this.initialValue,
}) : assert(displayStringForOption != null),
assert(optionsBuilder != null);
optionsBuilder 必须提供的参数
displayStringForOption 文字展示
fieldViewBuilder 输入内容构造器
onSelected 选中条目的回调
optionsMaxHeight 最大高度
optionsViewBuilder 面板构造器
final AutocompleteOptionsBuilder optionsBuilder; 可以通过onSelected回调监听选中的条目
typedef AutocompleteOptionsBuilder<T extends Object> = FutureOr<Iterable<T>> Function(TextEditingValue textEditingValue);
此函数是一个异步函数,我们可以在此函数内部进行网络请求、数据库查询等操作,返回一个 Iterable 的可迭代对象。
buildOptions 构造数据模型以及模拟搜索网络请求操作
Future<Iterable<User>> buildOptions(TextEditingValue textEditingValue) async {
if (textEditingValue.text == '') {
return const Iterable<User>.empty();
}
return searchByArgs(textEditingValue.text);
}
searchByArgs 模拟网络请求,通过 args 参数搜索数据:
Future<Iterable<User>> searchByArgs(String args) async {
// 模拟网络请求
await Future.delayed(const Duration(milliseconds: 200));
///初始化数据
const List<User> data = [
User('程咬金', false, 'icon_5.webp'),
User('李元芳', false, 'icon_6.webp'),
User('李白', false, 'icon_7.webp'),
User('武则天', true, 'icon_8.webp'),
User('王昭君', true, 'icon_6.webp'),
User('妲己', true, 'icon_8.webp'),
User('刘禅', false, 'icon_6.webp'),
User('张飞', false, 'icon_8.webp'),
User('刘邦', false, 'icon_5.webp'),
User('李信', false, 'icon_6.webp'),
User('anne', true, 'icon_8.webp'),
User('andy', true, 'icon_5.webp'),
User('joy', true, 'icon_7.webp'),
];
return data.where((User user) => user.name.contains(args));
}
buildOptions 完成输入–> 搜索 --> 展示联想词的流程。这也是 Autocomplete 组件最简单的使用
return Scaffold(
appBar: AppBar(
title: Text('Autocomplete'),
centerTitle: true,
),
body: SizedBox(
child: Container(
child: buildAutoComplete(),
),
),
);
效果如下:
自定义Autocomplete组件内容
return Scaffold(
appBar: AppBar(
title: Text('Autocomplete'),
centerTitle: true,
),
body: SizedBox(
height: 300,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: buildAutoComplete(),
),
),
),
);
}
定义组件内容
Widget buildAutoComplete() {
return Autocomplete<User>(
optionsBuilder: buildOptions,
onSelected: onSelected,
optionsViewBuilder: _buildOptionsView,
fieldViewBuilder: _buildFieldView,
displayStringForOption: (user) => user.name,
);
}
Autocomplete 中提供了 fieldViewBuilder 和 optionsViewBuilder 分别用于构造输入框和浮层面板。
_buildOptionsView 方法的实现,其中会回调 onSelected 回调函数,和 options 数据,我们需要做的就是依靠数据,构建组件进行展示即可。另外,默认浮层面板和输入框底部平齐,可以通过 Padding 进行下移。另外,由于是浮层,展示文字时,上面需要嵌套 Material 组件。
Widget _buildOptionsView(BuildContext context,
AutocompleteOnSelected<User> onSelected, Iterable<User> options) {
return Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Material(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150.0),
child: ListView.builder(
padding: EdgeInsets.zero,
itemBuilder: (_, index) {
final User option = options.elementAt(index);
return InkWell(
onTap: () => onSelected.call(option),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 6.0, horizontal: 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
foregroundColor: Colors.transparent,
backgroundImage: AssetImage('assets/head_icon/${option.image}'),
),
const SizedBox(
width: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text.rich(formSpan(
option.name, _textEditingController.text)),
Text(
'性别:${option.man ? '女' : '男'}',
style: const TextStyle(color: Colors.grey),
)
],
),
],
),
),
);
},
itemCount: options.length,
),
),
),
),
);
}
设置与输入框匹配的内容高亮
///高亮某些文字
final TextStyle lightTextStyle = const TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
);
InlineSpan formSpan(String src, String pattern) {
List<TextSpan> span = [];
List<String> parts = src.split(pattern);
if (parts.length > 1) {
for (int i = 0; i < parts.length; i++) {
span.add(TextSpan(text: parts[i]));
if (i != parts.length - 1) {
span.add(TextSpan(text: pattern, style: lightTextStyle));
}
}
} else {
span.add(TextSpan(text: src));
}
return TextSpan(children: span);
}
设置输入框样式:
_textEditingController 用于记录输入框的内容,_focusNode用于焦点的监听。
因为 optionsViewBuilder 回调中没有回调输入arg字符,所以_textEditingController用于记录输入的字符,在部分字体高亮下使用。
使用TextFormField 构建您想要的输入框样式
Widget _buildFieldView(
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
_textEditingController = textEditingController;
_focusNode = focusNode;
return SizedBox(
height: 34,
child: TextFormField(
controller: textEditingController,
focusNode: focusNode,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
fillColor: Color(0xFFF7F8FA),
prefixIcon: Icon(Icons.search),
filled: true,
contentPadding: EdgeInsets.only(top: 1.0),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(
Radius.circular(19.0),
),
),
),
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
),
);
}
构造数据模型
class User {
final String name;
final bool man;
final String image;
const User(this.name, this.man, this.image);
@override
String toString() {
return 'User{name:$name, man:$man, image:$image}';
}
}
最终效果如第一个图
以下是全部代码:
///构造模型
class User {
final String name;
final bool man;
final String image;
const User(this.name, this.man, this.image);
@override
String toString() {
return 'User{name:$name, man:$man, image:$image}';
}
}
class AutocompletePage extends StatelessWidget {
TextEditingController _textEditingController = TextEditingController();
FocusNode _focusNode = FocusNode();
User? user;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Autocomplete'),
centerTitle: true,
),
body: SizedBox(
height: 300,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: buildAutoComplete(),
),
),
),
);
}
Widget buildAutoComplete() {
return Autocomplete<User>(
optionsBuilder: buildOptions,
onSelected: onSelected,
optionsViewBuilder: _buildOptionsView,
fieldViewBuilder: _buildFieldView,
displayStringForOption: (user) => user.name,
);
}
Future<Iterable<User>> buildOptions(TextEditingValue textEditingValue) async {
if (textEditingValue.text == '') {
return const Iterable<User>.empty();
}
return searchByArgs(textEditingValue.text);
}
Future<Iterable<User>> searchByArgs(String args) async {
// 模拟网络请求
await Future.delayed(const Duration(milliseconds: 200));
const List<User> data = [
User('程咬金', false, 'icon_5.webp'),
User('李元芳', false, 'icon_6.webp'),
User('李白', false, 'icon_7.webp'),
User('武则天', true, 'icon_8.webp'),
User('王昭君', true, 'icon_6.webp'),
User('妲己', true, 'icon_8.webp'),
User('刘禅', false, 'icon_6.webp'),
User('张飞', false, 'icon_8.webp'),
User('刘邦', false, 'icon_5.webp'),
User('李信', false, 'icon_6.webp'),
User('anne', true, 'icon_8.webp'),
User('andy', true, 'icon_5.webp'),
User('joy', true, 'icon_7.webp'),
];
return data.where((User user) => user.name.contains(args));
}
onSelected(User options) {
_focusNode.unfocus(); //输入框失去焦点,收起键盘
print('选择结束:${options}');
}
Widget _buildOptionsView(BuildContext context,
AutocompleteOnSelected<User> onSelected, Iterable<User> options) {
return Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Material(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150.0),
child: ListView.builder(
padding: EdgeInsets.zero,
itemBuilder: (_, index) {
final User option = options.elementAt(index);
return InkWell(
onTap: () => onSelected.call(option),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 6.0, horizontal: 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
foregroundColor: Colors.transparent,
backgroundImage: AssetImage('assets/head_icon/${option.image}'),
),
const SizedBox(
width: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text.rich(formSpan(
option.name, _textEditingController.text)),
Text(
'性别:${option.man ? '女' : '男'}',
style: const TextStyle(color: Colors.grey),
)
],
),
],
),
),
);
},
itemCount: options.length,
),
),
),
),
);
}
///高亮某些文字
final TextStyle lightTextStyle = const TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
);
InlineSpan formSpan(String src, String pattern) {
List<TextSpan> span = [];
List<String> parts = src.split(pattern);
if (parts.length > 1) {
for (int i = 0; i < parts.length; i++) {
span.add(TextSpan(text: parts[i]));
if (i != parts.length - 1) {
span.add(TextSpan(text: pattern, style: lightTextStyle));
}
}
} else {
span.add(TextSpan(text: src));
}
return TextSpan(children: span);
}
Widget _buildFieldView(
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
_textEditingController = textEditingController;
_focusNode = focusNode;
return SizedBox(
height: 34,
child: TextFormField(
controller: textEditingController,
focusNode: focusNode,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
fillColor: Color(0xFFF7F8FA),
prefixIcon: Icon(Icons.search),
filled: true,
contentPadding: EdgeInsets.only(top: 1.0),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(
Radius.circular(19.0),
),
),
),
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
),
);
}
}
demo路径下的 lib/views/autocomplete
链接: demo地址(https://github.com/Iamhopemiracle/function_project).
demo中包含其他功能,使用get框架配置