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

Flutter Web:如何锁定TextFormField中的特定占位符区域

实现Flutter Web可编辑文本+锁定占位符的方案

这个需求完全可以实现,下面给你两种实用的实现思路:

方案一:拆分文本为可编辑段+占位符组件

把段落拆分成可编辑文本片段和不可编辑占位符,用Wrap(支持自动换行)组合起来,可编辑部分用无样式的TextField,占位符用普通Text组件锁定。

代码示例

import 'package:flutter/material.dart';

class EditableLeaseText extends StatelessWidget {
  const EditableLeaseText({super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Wrap(
        spacing: 4.0,
        runSpacing: 4.0,
        children: [
          _buildEditableSegment("Tenant shall have the option to extend the Term of the Lease for "),
          _buildLockedPlaceholder("{{additional.term.count}}"),
          _buildEditableSegment(" additional "),
          _buildLockedPlaceholder("{{term.length}}"),
          _buildEditableSegment(" term(s) with written notification by Tenant to Landlord "),
          _buildLockedPlaceholder("{{advance.written.notification}}"),
          _buildEditableSegment(" prior to Tenant's Lease expiration. The rental terms for the option periods shall be based on "),
          _buildLockedPlaceholder("{{market.rent.percentage}}"),
          _buildEditableSegment(" of the market rents including market concessions. In the event the Landlord and Tenant are unable to agree on "),
          _buildLockedPlaceholder("{{market.rent.percentage}}"),
          _buildEditableSegment(" of the market rent, the Landlord and Tenant shall proceed to a 3 broker arbitration method"),
        ],
      ),
    );
  }

  // 可编辑文本片段
  Widget _buildEditableSegment(String initialText) {
    return TextField(
      controller: TextEditingController(text: initialText),
      decoration: const InputDecoration(
        border: InputBorder.none,
        isDense: true,
        contentPadding: EdgeInsets.zero,
      ),
      style: const TextStyle(fontSize: 16, color: Colors.black87),
      maxLines: null,
    );
  }

  // 锁定的占位符
  Widget _buildLockedPlaceholder(String placeholder) {
    return Text(
      placeholder,
      style: const TextStyle(
        fontSize: 16,
        color: Colors.blueAccent,
        fontWeight: FontWeight.w600,
      ),
    );
  }
}

优点:占位符完全不可选中、不可编辑,逻辑简单易上手;缺点:文本被拆分为多个输入框,需要额外处理光标跨段跳转、整体文本导出等细节。

方案二:自定义文本控制器过滤输入

用单个TextField结合自定义TextEditingController,监听并过滤用户输入,确保占位符不被修改或删除。

代码示例

import 'package:flutter/material.dart';

class ProtectedPlaceholderController extends TextEditingController {
  final List<String> protectedPlaceholders;
  // 存储原始文本中占位符的位置信息(可优化为更精准的位置记录)
  final Map<String, List<int>> placeholderPositions = {};

  ProtectedPlaceholderController({
    required this.protectedPlaceholders,
    String? text,
  }) : super(text: text) {
    _recordPlaceholderPositions(text ?? '');
  }

  // 记录每个占位符在文本中的起始/结束索引
  void _recordPlaceholderPositions(String text) {
    for (var placeholder in protectedPlaceholders) {
      final regex = RegExp(RegExp.escape(placeholder));
      final matches = regex.allMatches(text);
      placeholderPositions[placeholder] = matches.map((m) => m.start).toList();
    }
  }

  @override
  set text(String newText) {
    String processedText = newText;
    // 检查并恢复所有被修改的占位符
    for (var placeholder in protectedPlaceholders) {
      // 若占位符缺失,根据记录的位置重新插入
      if (!processedText.contains(placeholder)) {
        for (var pos in placeholderPositions[placeholder] ?? []) {
          if (pos <= processedText.length) {
            processedText = processedText.substring(0, pos) + placeholder + processedText.substring(pos);
          }
        }
      }
    }
    super.text = processedText;
  }

  @override
  void updateEditingValue(TextEditingValue newValue) {
    // 拦截输入操作,防止占位符被修改
    String newText = newValue.text;
    for (var placeholder in protectedPlaceholders) {
      if (newText.contains(RegExp(r'{{[^}]+}}'))) {
        // 确保占位符格式不被破坏
        newText = newText.replaceAll(RegExp(r'{{[^}]+}}'), (match) {
          return protectedPlaceholders.contains(match.group(0)) ? match.group(0)! : placeholder;
        });
      }
    }
    super.updateEditingValue(newValue.copyWith(text: newText));
  }
}

// 使用示例
class SingleFieldEditableLease extends StatelessWidget {
  const SingleFieldEditableLease({super.key});

  final String initialText = "Tenant shall have the option to extend the Term of the Lease for {{additional.term.count}} additional {{term.length}} term(s) with written notification by Tenant to Landlord {{advance.written.notification}} prior to Tenant's Lease expiration. The rental terms for the option periods shall be based on {{market.rent.percentage}} of the market rents including market concessions. In the event the Landlord and Tenant are unable to agree on {{market.rent.percentage}} of the market rent, the Landlord and Tenant shall proceed to a 3 broker arbitration method";

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: TextField(
        controller: ProtectedPlaceholderController(
          protectedPlaceholders: [
            "{{additional.term.count}}",
            "{{term.length}}",
            "{{advance.written.notification}}",
            "{{market.rent.percentage}}",
          ],
          text: initialText,
        ),
        style: const TextStyle(fontSize: 16),
        maxLines: null,
        decoration: const InputDecoration(
          border: OutlineInputBorder(),
          hintText: "Edit the lease terms (placeholders are locked)",
        ),
      ),
    );
  }
}

优点:单个输入框,编辑体验流畅,便于导出完整文本;缺点:需要处理复杂的文本位置逻辑,确保占位符始终被保留。

适配Flutter Web的注意事项

  • 对于方案一的多TextField布局,在Web上可以通过设置TextField的focusNode来优化光标跳转体验;
  • 两种方案都可以通过TextStyle给占位符设置特殊样式(比如蓝色加粗),让用户清晰区分可编辑和锁定内容;
  • 导出文本时,方案一需要遍历所有TextField的控制器拼接内容,方案二直接取controller.text即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 21:37:49