如何在Flutter的TextFormField中嵌入可退格删除的图片
Flutter中实现可嵌入图片且支持退格删除的输入框
以下是几种可行的实现方案,满足将图片作为聊天内容一部分、支持退格删除的需求:
方案一:自定义TextEditingController + 富文本渲染
通过自定义TextEditingController维护文本与图片的混合数据源,重写富文本渲染逻辑,并处理退格事件:
- 定义数据源结构
创建类管理输入内容的分段(文本或图片):
class InputSegment { final bool isImage; final String? text; final ImageProvider? image; InputSegment.text(this.text) : isImage = false, image = null; InputSegment.image(this.image) : isImage = true, text = null; } class InputContent { final List<InputSegment> segments = []; }
- 自定义TextEditingController
重写buildTextSpan方法将图片分段渲染为WidgetSpan,同时实现退格处理逻辑:
class RichInputController extends TextEditingController { final InputContent content = InputContent(); @override TextSpan buildTextSpan({required BuildContext context, TextStyle? style, required bool withComposing}) { final children = <InlineSpan>[]; for (var segment in content.segments) { if (segment.isImage && segment.image != null) { children.add(WidgetSpan( child: Container( width: 40, height: 40, margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), image: DecorationImage(image: segment.image!, fit: BoxFit.cover), ), ), )); } else if (!segment.isImage && segment.text != null) { children.add(TextSpan(text: segment.text, style: style)); } } return TextSpan(style: style, children: children); } // 处理退格逻辑 void handleBackspace() { if (content.segments.isEmpty) return; final last = content.segments.last; if (last.isImage) { content.segments.removeLast(); } else if (last.text!.length > 1) { content.segments.removeLast(); content.segments.add(InputSegment.text(last.text!.substring(0, last.text!.length - 1))); } else { content.segments.removeLast(); } notifyListeners(); } // 添加文本分段 void addText(String text) { if (content.segments.isNotEmpty && !content.segments.last.isImage) { final lastText = content.segments.last.text!; content.segments.removeLast(); content.segments.add(InputSegment.text(lastText + text)); } else { content.segments.add(InputSegment.text(text)); } notifyListeners(); } // 添加图片分段 void addImage(ImageProvider image) { content.segments.add(InputSegment.image(image)); notifyListeners(); } }
- 在TextFormField中使用
监听键盘退格事件,同步文本输入与自定义数据源:
final _richController = RichInputController(); final _focusNode = FocusNode(); @override Widget build(BuildContext context) { return TextFormField( controller: _richController, focusNode: _focusNode, decoration: const InputDecoration(hintText: "输入消息或插入图片"), onChanged: (value) { // 过滤占位符,仅处理实际输入文本 final pureText = value.replaceAll(RegExp(r'\[img-\d+\]'), ''); if (pureText.isNotEmpty) { _richController.addText(pureText); // 重置controller的text为占位符,避免显示重复内容 _richController.text = _richController.content.segments.map((s) { return s.isImage ? '[img-${DateTime.now().microsecondsSinceEpoch}]' : s.text!; }).join(); } }, onKeyEvent: (node, event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.backspace) { _richController.handleBackspace(); // 更新controller的text占位符 _richController.text = _richController.content.segments.map((s) { return s.isImage ? '[img-${DateTime.now().microsecondsSinceEpoch}]' : s.text!; }).join(); return KeyEventResult.handled; } return KeyEventResult.ignored; }, ); }
方案二:基于flutter_quill快速实现
flutter_quill作为成熟的富文本编辑器,原生支持图片嵌入,只需自定义退格逻辑即可满足需求:
- 添加依赖
在pubspec.yaml中添加:
dependencies: flutter_quill: ^latest_version
- 初始化控制器并配置编辑器
final _quillController = QuillController.basic(); final _focusNode = FocusNode(); @override Widget build(BuildContext context) { return QuillEditor( controller: _quillController, focusNode: _focusNode, autoFocus: false, readOnly: false, expands: false, padding: EdgeInsets.zero, customStyles: DefaultStyles( paragraph: DefaultTextBlockStyle( const TextStyle(color: Colors.black, fontSize: 16), EdgeInsets.zero, EdgeInsets.zero, null, ), ), onKeyEvent: (event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.backspace) { final selection = _quillController.selection; if (selection.isCollapsed) { final pos = selection.baseOffset; final node = _quillController.document.queryNode(pos); // 判断当前节点是否为图片嵌入 if (node is ImageEmbed) { // 删除图片节点,需根据节点长度调整偏移 _quillController.delete(pos - 1, pos + 1); return KeyEventResult.handled; } } } return KeyEventResult.ignored; }, ); }
- 插入图片的方法
// 插入本地图片 void insertLocalImage() { _quillController.insertImage( FileImage(File('path/to/image.png')), width: 40, height: 40, ); } // 插入网络图片 void insertNetworkImage() { _quillController.insertImage( NetworkImage('https://example.com/image.png'), width: 40, height: 40, ); }
方案三:自定义混合输入组件(高度定制)
若需完全自定义UI,可通过Wrap组件混合TextField与图片Widget,手动维护输入内容列表:
- 用Wrap包裹多个子组件,每个组件为TextField(文本段)或Image(图片段)
- 监听退格事件,当当前TextField为空时,删除前一个组件
- 文本输入时动态更新TextField内容,或添加新的TextField段
此方案需处理光标切换、内容拼接等细节,适合对UI有极致定制需求的场景。
内容的提问来源于stack exchange,提问作者Sindu
相关产品推荐
相关产品推荐

