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

Flutter中readonly属性下切换TextField时键盘频繁弹收的解决求助

解决OTP输入框焦点切换时键盘闪烁问题

问题出在你用readOnly控制输入框的可编辑状态——当焦点切换时,原输入框从可编辑变为只读,系统会判定输入控件状态失效,先收起键盘,新输入框获得焦点后再重新唤起,导致键盘闪烁。

核心解决思路是让所有输入框保持可编辑状态,通过焦点控制输入行为,而非用readonly切换状态,修改后的完整代码如下:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class OTPTextField extends StatefulWidget {
  const OTPTextField();

  @override
  OTPTextFieldState createState() => OTPTextFieldState();
}

class OTPTextFieldState extends State<OTPTextField> {
  final List<TextEditingController> _controllers =
      List.generate(6, (_) => TextEditingController());
  final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());

  FocusNode keyBoardFocus = FocusNode();

  @override
  void initState() {
    super.initState();
    // 监听焦点变化,确保空输入框聚焦时光标正常显示
    for (int i = 0; i < _focusNodes.length; i++) {
      _focusNodes[i].addListener(() {
        if (_focusNodes[i].hasFocus && _controllers[i].text.isEmpty) {
          _controllers[i].text = "";
        }
      });
    }
  }

  void _onTextChanged(int index) {
    if (_controllers[index].text.length == 1 && index != 5) {
      // 输入完成后自动跳转到下一个输入框
      FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
    }
  }

  void onTextFieldTap(int index) {
    // 点击输入框直接聚焦目标
    FocusScope.of(context).requestFocus(_focusNodes[index]);
    // 已有内容时选中,方便快速修改
    if (_controllers[index].text.isNotEmpty) {
      _controllers[index].selection = TextSelection(
        baseOffset: 0,
        extentOffset: _controllers[index].text.length,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RawKeyboardListener(
        focusNode: keyBoardFocus,
        onKey: (keyEvent) {
          if (keyEvent.logicalKey == LogicalKeyboardKey.backspace &&
              keyEvent is RawKeyDownEvent) {
            // 找到当前聚焦的输入框索引
            final currentIndex = _focusNodes.indexWhere((node) => node.hasFocus);
            if (currentIndex > 0 && _controllers[currentIndex].text.isEmpty) {
              // 当前输入框为空时,退格跳转到上一个并清空内容
              FocusScope.of(context).requestFocus(_focusNodes[currentIndex - 1]);
              _controllers[currentIndex - 1].clear();
            }
          }
        },
        child: Center(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 20),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: List.generate(6, (index) {
                return SizedBox(
                  width: 45,
                  child: TextField(
                    maxLength: 1,
                    autofocus: index == 0,
                    onTap: () => onTextFieldTap(index),
                    cursorColor: Colors.black,
                    cursorWidth: 2,
                    cursorRadius: const Radius.circular(5),
                    controller: _controllers[index],
                    focusNode: _focusNodes[index],
                    decoration: InputDecoration(
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(5),
                      ),
                      counterText: "", // 隐藏输入长度提示
                      focusedBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(5),
                        borderSide: const BorderSide(color: Colors.blue, width: 2),
                      ),
                    ),
                    inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                    keyboardType: TextInputType.number,
                    textInputAction: index == 5
                        ? TextInputAction.done
                        : TextInputAction.next,
                    onChanged: (value) => _onTextChanged(index),
                    // 非聚焦时禁止选中编辑,仅保留点击跳转功能
                    enableInteractiveSelection: _focusNodes[index].hasFocus,
                  ),
                );
              }),
            ),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 清理资源,避免内存泄漏
    for (var controller in _controllers) {
      controller.dispose();
    }
    for (var node in _focusNodes) {
      node.dispose();
    }
    keyBoardFocus.dispose();
    super.dispose();
  }
}

关键修改说明:

  1. 移除readOnly属性:所有输入框保持可编辑状态,彻底避免状态切换导致的键盘波动
  2. 用enableInteractiveSelection控制编辑权限:非聚焦输入框禁止选中和编辑,但依然支持点击跳转焦点
  3. 基于焦点节点判断状态:不再维护_isActive数组,直接通过focusNode.hasFocus判断当前活跃输入框,减少状态同步问题
  4. 优化焦点跳转逻辑:输入完成自动跳转到下一个,点击输入框直接聚焦目标,退格时处理空输入框的跳转逻辑
  5. 添加资源清理:新增dispose方法清理控制器和焦点节点,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:10:29