You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TextFormField中嵌入内联Widget?

在TextFormField中嵌入内联Widget的实现方案

要在TextFormField中嵌入任意内联Widget,核心思路是通过自定义TextEditingController结合WidgetSpan来实现,逻辑类似extended_text_field处理图片的方式,只是将图片替换为自定义Widget。以下是具体实现步骤:

1. 自定义带Widget占位的TextEditingController

用特殊标记(比如[widget_xxx])作为文本中的占位符,同时维护映射表记录占位符对应的Widget。重写控制器的buildTextSpan方法,将占位符替换为WidgetSpan:

class WidgetTextEditingController extends TextEditingController {
  // 占位符与Widget的映射表
  final Map<String, Widget> widgetMap = {};
  // 占位符前缀,避免与普通文本冲突
  static const placeholderPrefix = '[widget_]';

  // 添加Widget并返回对应的占位符
  String addWidget(Widget widget) {
    final placeholder = '$placeholderPrefix${widgetMap.length}';
    widgetMap[placeholder] = widget;
    return placeholder;
  }

  @override
  TextSpan buildTextSpan({
    required BuildContext context,
    TextStyle? style,
    required bool withComposing,
  }) {
    final text = this.text;
    final spans = <InlineSpan>[];
    int currentIndex = 0;

    // 遍历文本,替换占位符为WidgetSpan
    while (currentIndex < text.length) {
      final placeholderStart = text.indexOf(placeholderPrefix, currentIndex);
      if (placeholderStart == -1) {
        // 剩余文本无占位符,直接添加普通文本
        spans.add(TextSpan(text: text.substring(currentIndex), style: style));
        break;
      }
      // 添加占位符前的普通文本
      if (placeholderStart > currentIndex) {
        spans.add(TextSpan(text: text.substring(currentIndex, placeholderStart), style: style));
      }
      // 找到占位符结束位置
      final placeholderEnd = text.indexOf(']', placeholderStart);
      if (placeholderEnd == -1) {
        // 占位符不完整,当作普通文本处理
        spans.add(TextSpan(text: text.substring(placeholderStart), style: style));
        break;
      }
      final placeholder = text.substring(placeholderStart, placeholderEnd + 1);
      // 替换为WidgetSpan
      final widget = widgetMap[placeholder];
      if (widget != null) {
        spans.add(WidgetSpan(
          child: widget,
          alignment: PlaceholderAlignment.middle,
        ));
      } else {
        spans.add(TextSpan(text: placeholder, style: style));
      }
      currentIndex = placeholderEnd + 1;
    }

    return TextSpan(children: spans, style: style);
  }
}

2. 在TextFormField中使用自定义控制器

将自定义控制器传入TextFormField,同时添加交互按钮动态插入Widget:

class InlineWidgetTextFormField extends StatefulWidget {
  const InlineWidgetTextFormField({super.key});

  @override
  State<InlineWidgetTextFormField> createState() => _InlineWidgetTextFormFieldState();
}

class _InlineWidgetTextFormFieldState extends State<InlineWidgetTextFormField> {
  late final WidgetTextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WidgetTextEditingController();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // 插入示例Widget(带颜色的容器)
  void insertWidget() {
    final placeholder = _controller.addWidget(
      Container(
        width: 24,
        height: 24,
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(4),
        ),
        alignment: Alignment.center,
        child: const Text('W', style: TextStyle(color: Colors.white)),
      ),
    );
    // 在光标位置插入占位符
    final selection = _controller.selection;
    final newText = _controller.text.replaceRange(
      selection.start,
      selection.end,
      placeholder,
    );
    _controller.text = newText;
    // 调整光标到占位符之后
    _controller.selection = selection.copyWith(
      baseOffset: selection.start + placeholder.length,
      extentOffset: selection.start + placeholder.length,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextFormField(
          controller: _controller,
          maxLines: null,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            hintText: '输入文本或点击按钮插入Widget',
          ),
        ),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: insertWidget,
          child: const Text('插入内联Widget'),
        ),
      ],
    );
  }
}

3. 关键注意事项

  • 占位符唯一性:用自增数字生成占位符,避免映射冲突。
  • 光标处理:插入Widget后调整光标位置,防止光标停在占位符中间。
  • 文本过滤:可重写控制器的text setter,限制用户修改占位符的格式,保证占位符完整。
  • Widget交互:嵌入的Widget可直接添加GestureDetector处理点击等事件,不影响输入框正常交互。

内容的提问来源于stack exchange,提问作者user3808307

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 09:40:23