Flutter自动完成文本输入插件type_ahead_text_field的使用
Flutter自动完成文本输入插件type_ahead_text_field的使用
核心功能
-
前缀触发回调
- 创建回调函数,当用户输入指定前缀(例如
@
、#
)时触发。此功能包括处理用户在输入更多内容时将光标移出包含匹配前缀的单词的情况。
- 创建回调函数,当用户输入指定前缀(例如
-
动态数据建议
- 根据检测到的前缀建议数据列表。例如,
@
用于朋友,#
用于热门标签。
- 根据检测到的前缀建议数据列表。例如,
-
多前缀检测
- 启用单个
TextField
处理多种类型的前缀,如@
、#
等。
- 启用单个
-
基于前缀的建议
- 根据检测到的前缀动态生成建议项。
-
上下文建议对话框
- 将建议对话框放置在检测到的前缀位置。确保当
TextField
光标直接位于前缀之后时显示对话框。一旦控制器检测到前缀,可以获得PrefixMatchState
。
- 将建议对话框放置在检测到的前缀位置。确保当
-
响应式建议对话框
- 随着用户输入调整建议对话框的位置,考虑
TextField
光标的偏移变化。对话框的 Y 偏移是可变的,而宽度保持不变(例如,类似于 Facebook 的建议对话框)。TypeAheadTextFieldController
还支持检查 X 偏移以进行更大程度的自定义。
- 随着用户输入调整建议对话框的位置,考虑
-
滚动处理
- 如果文本长度变得过长导致
TextField
滚动,则重新计算 X 偏移。这是通过提供TextField
控制器并动态检索位置来实现的。
- 如果文本长度变得过长导致
-
可定制的 TextSpan
- 自定义
TextSpan
以匹配不同前缀的单词。这包括添加动态数据,如UserModel
或TagModel
。CustomSpan
仅应用于已添加到批准数据的文本。
- 自定义
-
OnRemove 回调
- 监听
onRemove
回调以处理用户添加项目后删除文本的情况,允许根据已删除的内容进行适当的调整。
- 监听
示例代码
以下是 flutter-type-ahead-text-field
插件的完整示例代码,展示了如何实现上述功能:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:type_ahead_text_field/type_ahead_text_field.dart';
import 'package:rxdart/rxdart.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
[@override](/user/override)
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final TypeAheadTextFieldController controller;
final List<SuggestedDataWrapper> data = [];
OverlayEntry? overlayEntry;
final GlobalKey<EditableTextState> tfKey = GlobalKey();
final ValueNotifier<PrefixMatchState?> matchedState = ValueNotifier(null);
final List<String> users = ['john', 'josh', 'lucas', 'don', 'will'];
final List<String> tags = ['meme', 'challenge', 'city'];
PrefixMatchState? filterState;
GlobalKey suggestionWidgetKey = GlobalKey();
bool readOnly = false;
BehaviorSubject<PrefixMatchState?> bhMatchedState = BehaviorSubject();
[@override](/user/override)
void initState() {
super.initState();
// 初始化数据
data.addAll(users.map((e) => SuggestedDataWrapper(id: e, prefix: '@')));
data.addAll(tags.map((e) => SuggestedDataWrapper(id: e, prefix: '#')));
// 创建控制器
controller = TypeAheadTextFieldController(
appliedPrefixes: {'@', '#'}, // 应用的前缀
suggestibleData: data.toSet(), // 可建议的数据
textFieldKey: tfKey, // TextField 的 GlobalKey
edgePadding: EdgeInsets.all(6), // 边缘填充
onRemove: (data) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {});
});
},
customSpanBuilder: (SuggestedDataWrapper data) {
return customSpan(data); // 自定义 TextSpan
},
onStateChanged: (PrefixMatchState? state) {
if (state != null && (filterState == null || filterState != state)) {
filterState = state;
matchedState.value = state;
bhMatchedState.add(state);
}
if (state != null) {
if (overlayEntry == null) {
showSuggestionDialog(); // 显示建议对话框
}
} else {
removeOverlay(); // 移除建议对话框
}
},
);
}
void showSuggestionDialog() {
Size size = Size(200, 300); // 对话框大小
overlayEntry = OverlayEntry(builder: (context) {
return StreamBuilder<PrefixMatchState?>(
stream: controller.matchStateListStream, // 监听匹配状态
builder: (context, matchedState) {
List<SuggestedDataWrapper>? filteredData =
controller.matchedSuggestionListStream.value; // 获取过滤后的数据
Offset? offset = matchedState.data == null
? null
: controller.calculateGlobalOffset(
context: context,
localOffset: matchedState.data!.offset,
overlayContainerSize: size); // 计算全局偏移
return offset != null && filteredData != null
? Stack(
children: [
AnimatedPositioned(
key: suggestionWidgetKey,
duration: Duration(milliseconds: 250),
left: offset.dx,
top: offset.dy,
child: Material(
color: Colors.transparent,
child: Card(
child: Container(
height: 300,
width: 200,
child: Column(
children: [
Container(
height: 40,
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
removeOverlay(); // 关闭对话框
},
icon: Icon(Icons.close),
),
),
Text('Filter: ${filterState?.text}'), // 显示过滤条件
Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: 200),
child: ListView.builder(
itemBuilder: (context, index) {
var item = filteredData[index];
return GestureDetector(
child: ListTile(
title: Text('${item.id}'), // 显示建议项
),
onTap: () {
controller.approveSelection(
filterState!, item); // 确认选择
removeOverlay();
setState(() {});
},
);
},
itemCount: filteredData.length,
),
),
)
],
),
),
),
),
),
],
)
: Container();
},
);
});
Overlay.of(context).insert(overlayEntry!); // 插入 Overlay
}
TextSpan customSpan(SuggestedDataWrapper data) {
return TextSpan(
text: data.id,
style: TextStyle(color: Colors.blue), // 自定义样式
recognizer: readOnly
? (TapGestureRecognizer()
..onTap = () {
showAboutDialog(
context: context,
children: [Text('${data.prefix}${data.id}')]); // 长按显示详细信息
})
: null,
);
}
void removeOverlay() {
try {
overlayEntry?.remove(); // 移除 Overlay
overlayEntry = null;
} catch (e) {}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: [
Container(
child: Text(
'Added data: ${controller.getApprovedData().map((e) => '${e.prefix}${e.id}').toString()}',
style: TextStyle(color: Colors.blue), // 显示已添加的数据
),
padding: EdgeInsets.only(bottom: 24, top: 24),
),
Container(
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
padding: EdgeInsets.all(20),
margin: EdgeInsets.all(20),
child: TextField(
key: tfKey,
readOnly: readOnly, // 是否只读
scrollController: controller.scrollController, // 滚动控制器
controller: controller, // 文本控制器
maxLines: 6, // 最大行数
decoration: InputDecoration.collapsed(hintText: 'Description'), // 提示文本
cursorHeight: 14,
cursorWidth: 2,
onSubmitted: (s) {
removeOverlay(); // 提交时关闭建议对话框
},
),
),
ElevatedButton(
onPressed: () {
readOnly = !readOnly; // 切换编辑/保存模式
setState(() {});
},
child: Text('${readOnly ? 'EDIT' : 'SAVE'}'),
)
],
),
),
);
}
[@override](/user/override)
void dispose() {
removeOverlay(); // 清理 Overlay
super.dispose();
matchedState.dispose(); // 清理 ValueNotifier
}
}
更多关于Flutter自动完成文本输入插件type_ahead_text_field的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复
更多关于Flutter自动完成文本输入插件type_ahead_text_field的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用type_ahead_text_field
插件来实现自动完成文本输入的示例代码。这个插件可以帮助你在用户输入时提供建议列表,从而提高用户体验。
首先,确保你已经在pubspec.yaml
文件中添加了type_ahead_text_field
依赖:
dependencies:
flutter:
sdk: flutter
type_ahead_text_field: ^2.0.0 # 请根据需要调整版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Dart文件中使用TypeAheadTextField
组件。以下是一个完整的示例代码:
import 'package:flutter/material.dart';
import 'package:type_ahead_text_field/type_ahead_text_field.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TypeAheadTextField Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<String> suggestions = [
'Apple',
'Banana',
'Cherry',
'Date',
'Elderberry',
'Fig',
'Grape',
'Honeydew',
'Indian Fig',
'Jackfruit',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TypeAheadTextField Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TypeAheadField<String>(
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Search...',
suffixIcon: Icon(Icons.search),
),
),
suggestionsCallback: (pattern) async {
return suggestions.where((suggestion) =>
suggestion.toLowerCase().contains(pattern.toLowerCase())
).toList();
},
onSuggestionSelected: (suggestion) {
// 当用户选择一个建议时的回调
print("User selected: $suggestion");
},
),
),
);
}
}
代码解释
-
导入必要的包:
package:flutter/material.dart
用于Flutter的基础UI组件。package:type_ahead_text_field/type_ahead_text_field.dart
用于TypeAheadTextField
组件。
-
创建主应用:
MyApp
类定义了应用的入口,并设置了主题。
-
创建主页:
MyHomePage
是一个有状态的组件,用于管理UI的状态。- 在状态类中,定义了一个包含建议的字符串列表
suggestions
。
-
构建UI:
- 使用
Scaffold
和AppBar
创建基本的页面布局。 - 使用
Padding
添加一些内边距。 - 使用
TypeAheadField
组件来实现自动完成文本输入功能。textFieldConfiguration
用于配置文本字段的外观和行为。suggestionsCallback
是一个异步函数,根据用户输入的模式返回匹配的建议列表。onSuggestionSelected
是一个回调函数,当用户选择一个建议时执行。
- 使用
这个示例展示了如何使用type_ahead_text_field
插件来创建一个具有自动完成功能的文本输入字段。你可以根据需要对建议列表、UI样式和回调函数进行进一步的自定义。