After the ordinary TextField is switched to the password TextField, the password TextField will lose focus

73 Views Asked by At

Flutter (Channel stable, 3.13.9, on Microsoft Windows [版本 10.0.22621.4], locale zh-CN)

HUAWEI Mate 30 Pro 5G , HarmonyOS 4.0

The page has two TextFields, one for ordinary text and one for password. When the cursor jumps from the ordinary TextField to the password TextField, the password TextField will lose focus and the keyboard will switch to the secure keyboard. You need to click the password TextField again to gain focus.

I tried the following method but it still doesn't work

 final _accountFocus = FocusNode();
 final _passwordFocus = FocusNode();

Column(
      children: [
        Listener(
          onPointerDown: (e) => FocusScope.of(context).requestFocus(_accountFocus),
          child: TextField(
            focusNode: _accountFocus
          ),
        ),
        Listener(
          onPointerDown: (e) => FocusScope.of(context).requestFocus(_passwordFocus),
          child: TextField(
            focusNode: _passwordFocus,
            obscureText: true,
          ),
       ),
    ],
)
2

There are 2 best solutions below

1
On

Did you define these variables under the build() method?

final _accountFocus = FocusNode();
final _passwordFocus = FocusNode();

you should define these variables outside of build() method like this:

class _MyHomePageState extends State<MyHomePage> {
  final _accountFocus = FocusNode();
  final _passwordFocus = FocusNode();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            Listener(
              onPointerDown: (e) =>
                  FocusScope.of(context).requestFocus(_accountFocus),
              child: TextField(focusNode: _accountFocus),
            ),
            Listener(
              onPointerDown: (e) =>
                  FocusScope.of(context).requestFocus(_passwordFocus),
              child: TextField(
                focusNode: _passwordFocus,
                obscureText: true,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
0
On

I solved it indirectly using the following method.

By customizing TextInputFormatter, replace the input content with * and save the original content.

  1. Customize a ObscureTextEditingController

    class ObscureTextEditingController {
      final TextEditingController controller;
      final bool compatibleModel;
      final List<String> values;
    
      ObscureTextEditingController({
        this.compatibleModel = false,
      })  : values = <String>[],
            controller = TextEditingController();
    
      String get text {
        return compatibleModel ? values.join() : controller.text;
      }
    
      bool get isCompatibleModel => compatibleModel == true;
    
      set text(String newText) {
        controller.text = newText;
      }
    
      TextSelection get selection => controller.selection;
    
      set selection(TextSelection newSelection) {
        controller.selection = newSelection;
      }
    
      addListener(VoidCallback listener) {
        controller.addListener(listener);
      }
    
      dispose() {
        controller.dispose();
      }
    
      clear() {
        values.clear();
        controller.clear();
      }
    
      recoverText() {
        final cursor = controller.selection.base.offset;
        controller.text = values.join();
        controller.selection = TextSelection.collapsed(offset: cursor);
      }
    
      obscureText() {
        if (values.isNotEmpty) {
          final cursor = controller.selection.base.offset;
          StringBuffer buffer = StringBuffer();
          for (int i = 0; i < values.length; i++) {
            buffer.write("*");
          }
          controller.text = buffer.toString();
          controller.selection = TextSelection.collapsed(offset: cursor);
        }
      }
    }
    
    
  2. Customize a ObscureTextInputFormatter

    class ObscureTextInputFormatter extends TextInputFormatter {
      final List<String> values;
      final bool obscureText;
    
      ObscureTextInputFormatter(this.values, {required this.obscureText});
    
      @override
      TextEditingValue formatEditUpdate(
        TextEditingValue oldValue,
        TextEditingValue newValue,
      ) {
        //----------oldValue:---newValue:1-
        //----------oldValue:*---newValue:*2-
        //----------oldValue:**---newValue:**3-
        //----------oldValue:***---newValue:***4-
        final newStart = newValue.selection.start;
        final oldStart = oldValue.selection.start;
        final newEnd = newValue.selection.end;
        final oldEnd = oldValue.selection.end;
        print('----------oldValue:${oldValue.text}---newValue:${newValue.text}');
        print('new start:$newStart  end:$newEnd');
        print('old start:$oldStart  end:$oldEnd');
        if (newStart == newEnd && oldStart == oldEnd && newStart == oldStart) {
          return newValue;
        }
        if (newStart < oldStart) {
          // 删除
          values.removeAt(newStart);
        } else {
          // 新增
          print('新增开始位置:$oldStart  ${newValue.text.length}');
          final char = newValue.text.substring(oldStart, oldStart + 1);
          if (newStart == newValue.text.length) {
            print('末尾追加:$char');
            values.add(char);
          } else {
            values.insert(oldStart, char);
            print('插入:$char');
          }
        }
        // // 将输入框中每个字符替换为指定字符'*'
        String newText = newValue.text.replaceAll(RegExp(r'.'), '*');
        print('obscureText: $obscureText  实际值:$values');
    
        return TextEditingValue(
          text: obscureText ? newText : values.join(""),
          selection: newValue.selection,
          // selection: TextSelection.collapsed(offset: newText.length),
        );
      }
    }
    
    
  3. How to use

        final controller = ObscureTextEditingController(
           compatibleModel: true,
        );
    
        TextField(
            controller: controller.controller,
            // obscureText: _obscureText,
            inputFormatters: [
              ObscureTextInputFormatter(
                controller.values,
                obscureText: _obscureText,
              )
            ],
          )
    
  4. text/obscure Text

    void toggleEyes() {
        setState(() {
          _obscureText = !_obscureText;
          //  解决华为在切换密码输入框弹窗安全键盘丢失焦点bug
          if (controller.isCompatibleModel) {
            if (!_obscureText) {
              controller.recoverText();
            } else {
              controller.obscureText();
            }
          }
        });
      }