<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5bcc0gy"><img border="0" src="https://yellow-cdn.veclightyear.com/835a84d5/b63ff1fe-18b4-44c0-bd6b-f03ac7b2b0ce.png" alt="flutter-candies" title="flutter-candies"></a>
语言: 英文 | 简体中文
扩展官方文本字段以快速构建特殊文本,如内联图像、@某人、自定义背景等。它还支持构建自定义选择工具栏和句柄。
ExtendedTextField是Flutter官方TextField组件的第三方扩展库。主要扩展功能如下:
功能 | ExtendedTextField | TextField |
---|---|---|
内联图像和文本混合 | 支持,允许显示内联图像和混合文本 | 仅支持显示文本,但文本选择存在问题 |
复制实际值 | 支持,可以复制文 本的实际值 | 不支持 |
快速构建富文本 | 支持,可以基于文本格式快速构建富文本 | 不支持 |
支持
HarmonyOS
。请使用包含ohos
标签的最新版本。您可以在Versions
标签页中查看。
dependencies: extended_text_field: 11.0.1-ohos
请注意,以上翻译是基于您提供的原文信息。
不支持:当TextDirection.rtl时,不会处理特殊文本。
TextPainter计算的图像位置有问题。
不支持:当obscureText为true时,不会处理特殊文本。
extended text帮助快速将文本转换为特殊textSpan。
例如,以下代码展示了如何创建@xxxx特殊textSpan。
class AtText extends SpecialText { static const String flag = "@"; final int start; /// 是否为@somebody显示背景 final bool showAtBackground; AtText(TextStyle textStyle, SpecialTextGestureTapCallback onTap, {this.showAtBackground: false, this.start}) : super( flag, " ", textStyle, ); InlineSpan finishText() { TextStyle textStyle = this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); final String atText = toString(); return showAtBackground ? BackgroundTextSpan( background: Paint()..color = Colors.blue.withOpacity(0.15), text: atText, actualText: atText, start: start, ///光标可以移动到特殊文本中 deleteAll: true, style: textStyle, recognizer: (TapGestureRecognizer() ..onTap = () { if (onTap != null) onTap(atText); })) : SpecialTextSpan( text: atText, actualText: atText, start: start, style: textStyle, recognizer: (TapGestureRecognizer() ..onTap = () { if (onTap != null) onTap(atText); })); } }
创建您的SpecialTextSpanBuilder
class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { /// 是否为@somebody显示背景 final bool showAtBackground; final BuilderType type; MySpecialTextSpanBuilder( {this.showAtBackground: false, this.type: BuilderType.extendedText}); TextSpan build(String data, {TextStyle textStyle, onTap}) { var textSpan = super.build(data, textStyle: textStyle, onTap: onTap); return textSpan; } SpecialText createSpecialText(String flag, {TextStyle textStyle, SpecialTextGestureTapCallback onTap, int index}) { if (flag == null || flag == "") return null; ///index是起始标志的结束索引,所以文本起始索引应为index-(flag.length-1) if (isStart(flag, AtText.flag)) { return AtText(textStyle, onTap, start: index - (AtText.flag.length - 1), showAtBackground: showAtBackground, type: type); } else if (isStart(flag, EmojiText.flag)) { return EmojiText(textStyle, start: index - (EmojiText.flag.length - 1)); } else if (isStart(flag, DollarText.flag)) { return DollarText(textStyle, onTap, start: index - (DollarText.flag.length - 1), type: type); } return null; } }
通过使用ImageSpan显示内联图像。
ImageSpan( ImageProvider image, { Key key, double imageWidth, double imageHeight, EdgeInsets margin, int start: 0, ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom, String actualText, TextBaseline baseline, TextStyle style, BoxFit fit: BoxFit.scaleDown, ImageLoadingBuilder loadingBuilder, ImageFrameBuilder frameBuilder, String semanticLabel, bool excludeFromSemantics = false, Color color, BlendMode colorBlendMode, AlignmentGeometry imageAlignment = Alignment.center, ImageRepeat repeat = ImageRepeat.noRepeat, Rect centerSlice, bool matchTextDirection = false, bool gaplessPlayback = false, FilterQuality filterQuality = FilterQuality.low, }) ImageSpan(AssetImage("xxx.jpg"), imageWidth: size, imageHeight: size, margin: EdgeInsets.only(left: 2.0, bottom: 0.0, right: 2.0)); }
参数 | 描述 | 默认值 |
---|---|---|
image | 要显示的图像(ImageProvider)。 | - |
imageWidth | 图像宽度(不包括边距) | 必需 |
imageHeight | 图像高度(不包括边距) | 必需 |
margin | 图像的边距 | - |
actualText | 实际文本,启用选择时需要注意,类似于"[love]" | '\uFFFC' |
start | 文本的起始索引,启用选择时需要注意。 | 0 |
如果你想缓存网络图像,可以使用ExtendedNetworkImageProvider,并通过clearDiskCachedImages清除它们
导入extended_image_library
dependencies: extended_image_library: ^0.1.4
ExtendedNetworkImageProvider( this.url, { this.scale = 1.0, this.headers, this.cache: false, this.retries = 3, this.timeLimit, this.timeRetry = const Duration(milliseconds: 100), CancellationToken cancelToken, }) : assert(url != null), assert(scale != null), cancelToken = cancelToken ?? CancellationToken();
参数 | 描述 | 默认值 |
---|---|---|
url | 从中获取图像的URL。 | 必需 |
scale | 放置在图像[ImageInfo]对象中的缩放比例。 | 1.0 |
headers | 用于[HttpClient.get]从网络获取图像的HTTP头。 | - |
cache | 是否将图像缓存到本地 | false |
retries | 重试请求的次数 | 3 |
timeLimit | 请求图像的时间限制 | - |
timeRetry | 重试请求的时间间隔 | 毫秒:100 |
cancelToken | 用于取消网络请求的令牌 | CancellationToken() |
/// 清除磁盘缓存目录,然后返回是否成功。 /// <param name="duration">用于计算文件是否过期的时间跨度</param> Future<bool> clearDiskCachedImages({Duration duration}) async
重写[ExtendedTextField.extendedContextMenuBuilder]和[TextSelectionControls]以自定义你的工具栏小部件或处理小部件
const double _kHandleSize = 22.0; /// Android Material风格的文本选择控件。 class MyTextSelectionControls extends TextSelectionControls with TextSelectionHandleControls { static Widget defaultContextMenuBuilder( BuildContext context, ExtendedEditableTextState editableTextState) { return AdaptiveTextSelectionToolbar.buttonItems( buttonItems: <ContextMenuButtonItem>[ ...editableTextState.contextMenuButtonItems, ContextMenuButtonItem( onPressed: () { launchUrl( Uri.parse( 'mailto:zmtzawqlp@live.com?subject=extended_text_share&body=${editableTextState.textEditingValue.text}', ), ); editableTextState.hideToolbar(true); editableTextState.textEditingValue .copyWith(selection: const TextSelection.collapsed(offset: 0)); }, type: ContextMenuButtonType.custom, label: '喜欢', ), ], anchors: editableTextState.contextMenuAnchors, ); // return AdaptiveTextSelectionToolbar.editableText( // editableTextState: editableTextState, // ); } /// 返回Material句柄的大小。 Size getHandleSize(double textLineHeight) => const Size(_kHandleSize, _kHandleSize); /// Material风格文本选择句柄的构建器。 Widget buildHandle( BuildContext context, TextSelectionHandleType type, double textLineHeight, [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { final Widget handle = SizedBox( width: _kHandleSize, height: _kHandleSize, child: Image.asset( 'assets/40.png', ), ); // [handle]是一个圆圈,在该圆圈的左上象限有一个矩形(指向10:30的洋葱)。 // 我们旋转[handle]以根据句柄类型指向正上方或右上方。 switch (type) { case TextSelectionHandleType.left: // 指向右上方 return Transform.rotate( angle: math.pi / 4.0, child: handle, ); case TextSelectionHandleType.right: // 指向左上方 return Transform.rotate( angle: -math.pi / 4.0, child: handle, ); case TextSelectionHandleType.collapsed: // 指向上方 return handle; } } /// 获取Material风格文本选择句柄的锚点。 /// /// 参见 [TextSelectionControls.getHandleAnchor]。 Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, [double? startGlyphHeight, double? endGlyphHeight]) { switch (type) { case TextSelectionHandleType.left: return const Offset(_kHandleSize, 0); case TextSelectionHandleType.right: return Offset.zero; default: return const Offset(_kHandleSize / 2, -4); } } bool canSelectAll(TextSelectionDelegate delegate) { // Android允许在选择未折叠时全选,除非已经选择了所有内容。 final TextEditingValue value = delegate.textEditingValue; return delegate.selectAllEnabled && value.text.isNotEmpty && !(value.selection.start == 0 && value.selection.end == value.text.length); } } ## WidgetSpan  支持选择和点击测试 ExtendedWidgetSpan,你可以在 ExtendedTextField 中创建任何小部件。 ```dart class EmailText extends SpecialText { final TextEditingController controller; final int start; final BuildContext context; EmailText(TextStyle textStyle, SpecialTextGestureTapCallback onTap, {this.start, this.controller, this.context, String startFlag}) : super(startFlag, " ", textStyle, onTap: onTap); bool isEnd(String value) { var index = value.indexOf("@"); var index1 = value.indexOf("."); return index >= 0 && index1 >= 0 && index1 > index + 1 && super.isEnd(value); } InlineSpan finishText() { final String text = toString(); return ExtendedWidgetSpan( actualText: text, start: start, alignment: ui.PlaceholderAlignment.middle, child: GestureDetector( child: Padding( padding: EdgeInsets.only(right: 5.0, top: 2.0, bottom: 2.0), child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(5.0)), child: Container( padding: EdgeInsets.all(5.0), color: Colors.orange, child: Row( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: <Widget>[ Text( text.trim(), //style: textStyle?.copyWith(color: Colors.orange), ), SizedBox( width: 5.0, ), InkWell( child: Icon( Icons.close, size: 15.0, ), onTap: () { controller.value = controller.value.copyWith( text: controller.text .replaceRange(start, start + text.length, ""), selection: TextSelection.fromPosition( TextPosition(offset: start))); }, ) ], ), )), ), onTap: () { showDialog( context: context, barrierDismissible: true, builder: (c) { TextEditingController textEditingController = TextEditingController()..text = text.trim(); return Column( children: <Widget>[ Expanded( child: Container(), ), Material( child: Padding( padding: EdgeInsets.all(10.0), child: TextField( controller: textEditingController, decoration: InputDecoration( suffixIcon: FlatButton( child: Text("OK"), onPressed: () { controller.value = controller.value.copyWith( text: controller.text.replaceRange( start, start + text.length, textEditingController.text + " "), selection: TextSelection.fromPosition( TextPosition( offset: start + (textEditingController.text + " ") .length))); Navigator.pop(context); }, )), ), )), Expanded( child: Container(), ) ], ); }); }, ), deleteAll: true, ); } }
支持在不侵入任何代码的情况下阻止系统键盘显示,适用于 [ExtendedTextField] 或 [TextField]。
我们通过阻止 Flutter Framework 向 Flutter Engine 发送 TextInput.show
消息来防止系统键盘显示。
你可以直接使用 [TextInputBinding]。
void main() { TextInputBinding(); runApp(const MyApp()); }
或者如果你有其他 binding
,你可以按以下方式操作。
class YourBinding extends WidgetsFlutterBinding with TextInputBindingMixin,YourBindingMixin { } void main() { YourBinding(); runApp(const MyApp()); }
或者如果你需要覆盖 ignoreTextInputShow
,你可以按以下方式操作。
class YourBinding extends TextInputBinding { // ignore: unnecessary_overrides bool ignoreTextInputShow() { // 你可以根据你的情况覆盖它 // 如果 NoKeyboardFocusNode 不够用的话 return super.ignoreTextInputShow(); } } void main() { YourBinding(); runApp(const MyApp()); }
你应该将 [TextInputFocusNode] 传递给 [ExtendedTextField] 或 [TextField]。
final TextInputFocusNode _focusNode = TextInputFocusNode(); Widget build(BuildContext context) { return ExtendedTextField( // 需要时请求键盘 focusNode: _focusNode..debugLabel = 'ExtendedTextField', ); } Widget build(BuildContext context) { return TextField( // 需要时请求键盘 focusNode: _focusNode..debugLabel = 'CustomTextField', ); }
我们根据当前焦点是 [TextInputFocusNode] 且 ignoreSystemKeyboardShow
为 true 来防止系统键盘显示。
final FocusNode? focus = FocusManager.instance.primaryFocus; if (focus != null && focus is TextInputFocusNode && focus.ignoreSystemKeyboardShow) { return true; }
当 [TextInputFocusNode] 焦点改变时显示/隐藏你的自定义键盘。
如果你的自定义键盘可以在不失去焦点的情况下关闭,你还需要处理在 [ExtendedTextField] 或 [TextField] 的 onTap
事件中显示自定义键盘。
void initState() { super.initState(); _focusNode.addListener(_handleFocusChanged); } void _onTextFiledTap() { if (_bottomSheetController == null) { _handleFocusChanged(); } } void _handleFocusChanged() { if (_focusNode.hasFocus) { // 只是演示,你可以根据需要定义你的自定义键盘 _bottomSheetController = showBottomSheet<void>( context: FocusManager.instance.primaryFocus!.context!, // 如果不想通过拖动来关闭自定义键盘,设置为 false enableDrag: true, builder: (BuildContext b) { // 你的自定义键盘 return Container(); }); // 可能通过拖动关闭 _bottomSheetController?.closed.whenComplete(() { _bottomSheetController = null; }); } else { _bottomSheetController?.close(); _bottomSheetController = null; } } void dispose() { _focusNode.removeListener(_handleFocusChanged); super.dispose(); }
查看完整演示
AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI小说写作助手,一站式润色、改写、扩写
蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定, 还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号