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

Android Compose TextField实现拼写错误单词下划线功能

实现带拼写检查与错误高亮的TextField

要实现类似Word的拼写错误高亮+点击纠错功能,核心是实时文本校验+自定义文本样式+点击交互处理,以下分平台给出具体实现方案:

Android 原生实现方案

1. 自定义波浪线高亮样式

通过自定义Span实现红色波浪下划线效果,替代系统默认下划线:

class WaveUnderlineSpan(private val color: Int) : UnderlineSpan() {
    override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
        val wavePaint = Paint(paint).apply {
            this.color = color
            style = Paint.Style.STROKE
            strokeWidth = 2f
            pathEffect = PathEffect.createDashPathEffect(floatArrayOf(10f, 5f), 0f)
        }
        canvas.drawLine(x, bottom.toFloat(), x + paint.measureText(text, start, end), bottom.toFloat(), wavePaint)
    }
}

2. 实时监听输入并校验拼写

用TextWatcher监听输入变化,拆分文本为单词后调用系统拼写检查服务,对错误单词应用波浪线:

editText.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    override fun afterTextChanged(s: Editable?) {
        s ?: return
        // 清除旧的高亮样式
        s.getSpans(0, s.length, WaveUnderlineSpan::class.java).forEach { s.removeSpan(it) }
        
        // 拆分单词(可优化为支持标点的分词逻辑)
        val words = s.toString().split("\\s+".toRegex())
        var currentPos = 0
        words.forEach { word ->
            if (word.isNotEmpty()) {
                val wordStart = currentPos
                val wordEnd = currentPos + word.length
                // 校验单词拼写
                if (!isWordValid(word)) {
                    s.setSpan(WaveUnderlineSpan(Color.RED), wordStart, wordEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                }
                currentPos += word.length + 1 // 包含空格长度
            }
        }
    }
})

3. 点击错误单词显示推荐词

给EditText添加点击事件,判断点击位置是否在错误单词范围内,弹出推荐词选择弹窗:

editText.setOnClickListener { event ->
    val offset = editText.getOffsetForPosition(event.x, event.y)
    val spans = editText.text.getSpans(offset, offset, WaveUnderlineSpan::class.java)
    if (spans.isNotEmpty()) {
        val start = editText.text.getSpanStart(spans[0])
        val end = editText.text.getSpanEnd(spans[0])
        val wrongWord = editText.text.substring(start, end)
        // 获取拼写推荐词
        val suggestions = fetchSpellSuggestions(wrongWord)
        // 弹窗展示推荐词
        AlertDialog.Builder(context)
            .setItems(suggestions.toTypedArray()) { _, index ->
                editText.text.replace(start, end, suggestions[index])
            }
            .show()
    }
}

4. 调用系统拼写检查服务

使用Android原生SpellCheckerSession获取官方拼写建议:

private fun fetchSpellSuggestions(word: String): List<String> {
    val suggestions = mutableListOf<String>()
    val session = SpellCheckerSession(context, null, object : SpellCheckerSession.SpellCheckerSessionListener {
        override fun onGetSuggestions(results: Array<out SuggestionsInfo>?) {
            results?.firstOrNull()?.let { info ->
                for (i in 0 until info.getSuggestionsCount()) {
                    suggestions.add(info.getSuggestionAt(i))
                }
            }
            session.close()
        }
        override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {}
    }, true)
    session.getSuggestions(arrayOf(word), 5) // 请求最多5条建议
    return suggestions
}

Flutter 跨平台实现方案

依赖第三方拼写检查库,结合RichText实现高亮与交互:

1. 添加依赖

在pubspec.yaml中引入拼写检查库:

dependencies:
  spell_checker: ^0.4.0

2. 自定义拼写检查TextField

通过TextEditingController监听输入,生成带高亮样式的TextSpan,并处理点击事件:

class SpellCheckField extends StatefulWidget {
  @override
  _SpellCheckFieldState createState() => _SpellCheckFieldState();
}

class _SpellCheckFieldState extends State<SpellCheckField> {
  final TextEditingController _controller = TextEditingController();
  final SpellChecker _spellChecker = SpellChecker();
  List<TextSpan> _styledText = [];

  @override
  void initState() {
    super.initState();
    _controller.addListener(_updateStyledText);
    _spellChecker.initialize();
  }

  void _updateStyledText() {
    final text = _controller.text;
    final words = text.split(RegExp(r'\s+'));
    _styledText.clear();
    int currentPos = 0;

    for (final word in words) {
      if (word.isEmpty) {
        _styledText.add(const TextSpan(text: ' '));
        currentPos += 1;
        continue;
      }

      final isCorrect = _spellChecker.isCorrect(word);
      final style = isCorrect
          ? const TextStyle(color: Colors.black)
          : const TextStyle(
              color: Colors.black,
              decoration: TextDecoration.underline,
              decorationColor: Colors.red,
              decorationStyle: TextDecorationStyle.wavy,
            );

      _styledText.add(TextSpan(
        text: word,
        style: style,
        recognizer: TapGestureRecognizer()
          ..onTap = () => _showSuggestions(word),
      ));

      if (currentPos + word.length < text.length) {
        _styledText.add(const TextSpan(text: ' '));
        currentPos += word.length + 1;
      } else {
        currentPos += word.length;
      }
    }
    setState(() {});
  }

  void _showSuggestions(String wrongWord) async {
    final suggestions = await _spellChecker.getSuggestions(wrongWord);
    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: const Text('拼写建议'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: suggestions.map((s) => TextButton(
            onPressed: () {
              _controller.text = _controller.text.replaceFirst(wrongWord, s);
              Navigator.pop(context);
            },
            child: Text(s),
          )).toList(),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: const InputDecoration(hintText: '输入文本...'),
      // 如需完全自定义显示,可使用EditableText配合TextSpan
    );
  }
}

关键注意事项

  • 分词逻辑优化:不能仅按空格拆分,需处理标点符号(如"test,"应提取"test"校验)
  • 性能优化:避免每次输入全量校验,仅针对新增/修改的单词做检查
  • 平台适配:Android/iOS拼写检查API不同,需分别处理权限与服务调用
  • 体验优化:高亮样式不能遮挡输入,推荐词弹窗需简洁不干扰输入流程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 19:10:28